kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Spruce up `LoRaConfigScreen` (#3224)
rodzic
8c16052229
commit
3951ebb375
|
@ -194,6 +194,7 @@
|
|||
<ID>ModifierMissing:EmptyStateContent.kt$EmptyStateContent</ID>
|
||||
<ID>ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen</ID>
|
||||
<ID>ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen</ID>
|
||||
<ID>ModifierMissing:LoRaConfigItemList.kt$LoRaConfigScreen</ID>
|
||||
<ID>ModifierMissing:Main.kt$MainScreen</ID>
|
||||
<ID>ModifierMissing:MapReportingPreference.kt$MapReportingPreference</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$MessageStatusButton</ID>
|
||||
|
@ -210,6 +211,7 @@
|
|||
<ID>ModifierMissing:PositionLog.kt$PositionItem</ID>
|
||||
<ID>ModifierMissing:PositionLog.kt$PositionLogScreen</ID>
|
||||
<ID>ModifierMissing:PowerMetrics.kt$PowerMetricsScreen</ID>
|
||||
<ID>ModifierMissing:PreferenceDivider.kt$PreferenceDivider</ID>
|
||||
<ID>ModifierMissing:RadioConfig.kt$RadioConfigItemList</ID>
|
||||
<ID>ModifierMissing:RadioConfigScreenList.kt$RadioConfigScreenList</ID>
|
||||
<ID>ModifierMissing:Reaction.kt$ReactionDialog</ID>
|
||||
|
@ -393,8 +395,8 @@
|
|||
<ID>TopLevelPropertyNaming:Constants.kt$const val prefix = "com.geeksville.mesh"</ID>
|
||||
<ID>UnusedParameter:ChannelSettingsItemList.kt$onBack: () -> Unit</ID>
|
||||
<ID>UnusedParameter:ChannelSettingsItemList.kt$title: String</ID>
|
||||
<ID>UnusedParameter:DropDownPreference.kt$modifier: Modifier = Modifier</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$MainAppBar( viewModel = uIViewModel, navController = navController, onAction = { action -> when (action) { is NodeMenuAction.MoreDetails -> { navController.navigate( NodesRoutes.NodeDetailGraph(action.node.num), { launchSingleTop = true restoreState = true }, ) } is NodeMenuAction.Share -> sharedContact = action.node else -> {} } }, )</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$ScannedQrCodeDialog(uIViewModel, newChannelSet)</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
|
||||
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>
|
||||
|
|
|
@ -32,6 +32,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.protobuf.ProtocolMessageEnum
|
||||
|
@ -90,18 +91,29 @@ fun <T> DropDownPreference(
|
|||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
modifier = modifier.padding(vertical = 8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = items.firstOrNull { it.first == selectedItem }?.second ?: "",
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
prefix = { Text(title) },
|
||||
suffix = { Text(items.firstOrNull { it.first == selectedItem }?.second ?: "") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
colors =
|
||||
ExposedDropdownMenuDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
),
|
||||
enabled = enabled,
|
||||
supportingText = { if (summary != null) Text(text = summary) },
|
||||
supportingText =
|
||||
if (summary != null) {
|
||||
{ Text(text = summary, modifier = Modifier.padding(bottom = 8.dp)) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun PreferenceDivider() {
|
||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
}
|
|
@ -17,31 +17,36 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceDivider
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.ChannelOption
|
||||
import org.meshtastic.core.model.RegionInfo
|
||||
import org.meshtastic.core.model.numChannels
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SignedIntegerEditTextPreference
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
|
||||
@Composable
|
||||
fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
@ -65,184 +70,182 @@ fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewMod
|
|||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
summary = stringResource(id = R.string.config_lora_region_summary),
|
||||
enabled = state.connected,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = formState.value.region,
|
||||
onItemSelected = { formState.value = formState.value.copy { region = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_modem_preset),
|
||||
checked = formState.value.usePreset,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (formState.value.usePreset) {
|
||||
item {
|
||||
TitledCard(title = stringResource(R.string.options)) {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
summary = stringResource(id = R.string.config_lora_modem_preset_summary),
|
||||
enabled = state.connected && formState.value.usePreset,
|
||||
items =
|
||||
LoRaConfig.ModemPreset.entries
|
||||
.filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = formState.value.modemPreset,
|
||||
onItemSelected = { formState.value = formState.value.copy { modemPreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
} else {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.bandwidth),
|
||||
value = formState.value.bandwidth,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { bandwidth = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.spread_factor),
|
||||
value = formState.value.spreadFactor,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { spreadFactor = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.coding_rate),
|
||||
value = formState.value.codingRate,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { codingRate = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.advanced)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = formState.value.ignoreMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = formState.value.configOkToMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tx_enabled),
|
||||
checked = formState.value.txEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { txEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
summary = stringResource(id = R.string.config_lora_hop_limit_summary),
|
||||
value = formState.value.hopLimit,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { hopLimit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_slot),
|
||||
summary = stringResource(id = R.string.config_lora_frequency_slot_summary),
|
||||
value =
|
||||
if (isFocused || formState.value.channelNum != 0) {
|
||||
formState.value.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = {
|
||||
if (it <= formState.value.numChannels) { // total num of LoRa channels
|
||||
formState.value = formState.value.copy { channelNum = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
checked = formState.value.sx126XRxBoostedGain,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sx126XRxBoostedGain = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.override_frequency_mhz),
|
||||
value =
|
||||
if (isFocused || formState.value.overrideFrequency != 0f) {
|
||||
formState.value.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = { formState.value = formState.value.copy { overrideFrequency = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = formState.value.txPower,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { txPower = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (viewModel.hasPaFan) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.pa_fan_disabled),
|
||||
checked = formState.value.paFanDisabled,
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
summary = stringResource(id = R.string.config_lora_region_summary),
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { paFanDisabled = it } },
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = formState.value.region,
|
||||
onItemSelected = { formState.value = formState.value.copy { region = it } },
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_modem_preset),
|
||||
checked = formState.value.usePreset,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePreset = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
if (formState.value.usePreset) {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
summary = stringResource(id = R.string.config_lora_modem_preset_summary),
|
||||
enabled = state.connected && formState.value.usePreset,
|
||||
items = ChannelOption.entries.map { it.modemPreset to stringResource(it.labelRes) },
|
||||
selectedItem = formState.value.modemPreset,
|
||||
onItemSelected = { formState.value = formState.value.copy { modemPreset = it } },
|
||||
)
|
||||
} else {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.bandwidth),
|
||||
value = formState.value.bandwidth,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { bandwidth = it } },
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.spread_factor),
|
||||
value = formState.value.spreadFactor,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { spreadFactor = it } },
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.coding_rate),
|
||||
value = formState.value.codingRate,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { codingRate = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item { Spacer(modifier = Modifier.height(16.dp)) }
|
||||
|
||||
item {
|
||||
TitledCard(title = stringResource(R.string.advanced)) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = formState.value.ignoreMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ignoreMqtt = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = formState.value.configOkToMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { configOkToMqtt = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tx_enabled),
|
||||
checked = formState.value.txEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { txEnabled = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
summary = stringResource(id = R.string.config_lora_hop_limit_summary),
|
||||
value = formState.value.hopLimit,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { hopLimit = it } },
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
var isFocusedSlot by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_slot),
|
||||
summary = stringResource(id = R.string.config_lora_frequency_slot_summary),
|
||||
value =
|
||||
if (isFocusedSlot || formState.value.channelNum != 0) {
|
||||
formState.value.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocusedSlot = it.isFocused },
|
||||
onValueChanged = {
|
||||
if (it <= formState.value.numChannels) { // total num of LoRa channels
|
||||
formState.value = formState.value.copy { channelNum = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
checked = formState.value.sx126XRxBoostedGain,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sx126XRxBoostedGain = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
var isFocusedOverride by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.override_frequency_mhz),
|
||||
value =
|
||||
if (isFocusedOverride || formState.value.overrideFrequency != 0f) {
|
||||
formState.value.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocusedOverride = it.isFocused },
|
||||
onValueChanged = { formState.value = formState.value.copy { overrideFrequency = it } },
|
||||
)
|
||||
|
||||
PreferenceDivider()
|
||||
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = formState.value.txPower,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { txPower = it } },
|
||||
)
|
||||
|
||||
if (viewModel.hasPaFan) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.pa_fan_disabled),
|
||||
checked = formState.value.paFanDisabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { paFanDisabled = it } },
|
||||
containerColor = Color.Transparent,
|
||||
)
|
||||
}
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
@ -25,9 +27,12 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.settings.radio.ResponseState
|
||||
import com.google.protobuf.MessageLite
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.PreferenceFooter
|
||||
|
||||
@Composable
|
||||
|
@ -61,21 +66,24 @@ fun <T : MessageLite> RadioConfigScreenList(
|
|||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
content()
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && configState.isDirty,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
configState.reset()
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSave(configState.value)
|
||||
},
|
||||
)
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().weight(1f), contentPadding = PaddingValues(16.dp)) {
|
||||
content()
|
||||
}
|
||||
|
||||
PreferenceFooter(
|
||||
enabled = enabled && configState.isDirty,
|
||||
negativeText = stringResource(R.string.discard_changes),
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
configState.reset()
|
||||
},
|
||||
positiveText = stringResource(R.string.save_changes),
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSave(configState.value)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ android {
|
|||
dependencies {
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.strings)
|
||||
implementation(libs.annotation)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.zxing.android.embedded) { isTransitive = false }
|
||||
implementation(libs.zxing.core)
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.ModemPreset
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig.RegionCode
|
||||
import org.meshtastic.core.strings.R
|
||||
import kotlin.math.floor
|
||||
|
||||
/** hash a string into an integer using the djb2 algorithm by Dan Bernstein http://www.cse.yorku.ca/~oz/hash.html */
|
||||
|
@ -294,14 +296,14 @@ enum class RegionInfo(
|
|||
}
|
||||
}
|
||||
|
||||
enum class ChannelOption(val modemPreset: ModemPreset, val bandwidth: Float) {
|
||||
SHORT_TURBO(ModemPreset.SHORT_TURBO, bandwidth = .500f),
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, .250f),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, .250f),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, .250f),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, .250f),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, .250f),
|
||||
LONG_MODERATE(ModemPreset.LONG_MODERATE, .125f),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, .125f),
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, .0625f),
|
||||
enum class ChannelOption(val modemPreset: ModemPreset, @StringRes val labelRes: Int, val bandwidth: Float) {
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.label_very_long_slow, .0625f),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, R.string.label_long_fast, .250f),
|
||||
LONG_MODERATE(ModemPreset.LONG_MODERATE, R.string.label_long_moderate, .125f),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.label_long_slow, .125f),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, R.string.label_medium_fast, .250f),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, R.string.label_medium_slow, .250f),
|
||||
SHORT_TURBO(ModemPreset.SHORT_TURBO, R.string.label_short_turbo, bandwidth = .500f),
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, R.string.label_short_fast, .250f),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, R.string.label_short_slow, .250f),
|
||||
}
|
||||
|
|
|
@ -125,6 +125,16 @@
|
|||
<string name="config_lora_hop_limit_summary">Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. 0 hop broadcast messages will not get ACKs.</string>
|
||||
<string name="config_lora_frequency_slot_summary">Your node’s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name and will change from the default public slot. Change back to the public default slot if private primary and public secondary channels are configured.</string>
|
||||
|
||||
<string name="label_very_long_slow">Very Long Range - Slow</string>
|
||||
<string name="label_long_fast">Long Range - Fast</string>
|
||||
<string name="label_long_moderate">Long Range - Moderate</string>
|
||||
<string name="label_long_slow">Long Range - Slow</string>
|
||||
<string name="label_medium_fast">Medium Range - Fast</string>
|
||||
<string name="label_medium_slow">Medium Range - Slow</string>
|
||||
<string name="label_short_turbo">Short Range - Turbo</string>
|
||||
<string name="label_short_fast">Short Range - Fast</string>
|
||||
<string name="label_short_slow">Short Range - Slow</string>
|
||||
|
||||
<string name="config_network_wifi_enabled_summary">Enabling WiFi will disable the bluetooth connection to the app.</string>
|
||||
<string name="config_network_eth_enabled_summary">Enabling Ethernet will disable the bluetooth connection to the app. TCP node connections are not available on Apple devices.</string>
|
||||
<string name="config_network_udp_enabled_summary">Enable broadcasting packets via UDP over the local network.</string>
|
||||
|
@ -174,7 +184,8 @@
|
|||
<string name="analytics_okay">Allow analytics and crash reporting.</string>
|
||||
<string name="accept">Accept</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="clear_changes">Clear changes</string>
|
||||
<string name="discard_changes">Discard changes</string>
|
||||
<string name="save_changes">Save</string>
|
||||
<string name="new_channel_rcvd">New Channel URL received</string>
|
||||
<string name="permission_missing">Meshtastic needs location permissions enabled to find new devices via Bluetooth. You can disable when not in use.</string>
|
||||
<string name="report_bug">Report Bug</string>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<ID>ComposableParamOrder:MaterialBatteryInfo.kt$MaterialBatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:SwitchPreference.kt$SwitchPreference</ID>
|
||||
<ID>ContentSlotReused:AdaptiveTwoPane.kt$second</ID>
|
||||
<ID>LongMethod:EditTextPreference.kt$@Composable fun EditTextPreference( title: String, value: String, enabled: Boolean, isError: Boolean, keyboardOptions: KeyboardOptions, keyboardActions: KeyboardActions, onValueChanged: (String) -> Unit, modifier: Modifier = Modifier, summary: String? = null, maxSize: Int = 0, // max_size - 1 (in bytes) onFocusChanged: (FocusState) -> Unit = {}, trailingIcon: (@Composable () -> Unit)? = null, visualTransformation: VisualTransformation = VisualTransformation.None, )</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$100</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$101</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$14</ID>
|
||||
|
@ -43,10 +44,8 @@
|
|||
<ID>ParameterNaming:EditIPv4Preference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditPasswordPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditTextPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onCancelClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onNegativeClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onPositiveClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:SlidingSelector.kt$onOptionSelected</ID>
|
||||
<ID>PreviewPublic:BatteryInfo.kt$BatteryInfoPreview</ID>
|
||||
<ID>PreviewPublic:BatteryInfo.kt$BatteryInfoPreviewSimple</ID>
|
||||
|
|
|
@ -26,8 +26,10 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -38,10 +40,12 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusState
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
@ -207,7 +211,7 @@ fun EditTextPreference(
|
|||
) {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
|
||||
Column(modifier = modifier.padding(vertical = 8.dp)) {
|
||||
Column(modifier = modifier) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
|
@ -227,28 +231,39 @@ fun EditTextPreference(
|
|||
onValueChanged(it)
|
||||
}
|
||||
},
|
||||
label = { Text(title) },
|
||||
prefix = { Text(title) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
visualTransformation = visualTransformation,
|
||||
trailingIcon = {
|
||||
if (trailingIcon != null) {
|
||||
trailingIcon()
|
||||
} else if (isError) {
|
||||
trailingIcon =
|
||||
if (trailingIcon != null) {
|
||||
{ trailingIcon() }
|
||||
} else if (isError) {
|
||||
{
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Info,
|
||||
contentDescription = stringResource(id = R.string.error),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.End),
|
||||
)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp, end = 16.dp, bottom = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -30,25 +32,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
onCancelClicked: () -> Unit,
|
||||
onSaveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PreferenceFooter(
|
||||
enabled = enabled,
|
||||
negativeText = R.string.clear_changes,
|
||||
onNegativeClicked = onCancelClicked,
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = onSaveClicked,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated(message = "Use overload that accepts Strings for button text.")
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
|
@ -57,17 +42,36 @@ fun PreferenceFooter(
|
|||
@StringRes positiveText: Int,
|
||||
onPositiveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PreferenceFooter(
|
||||
enabled = enabled,
|
||||
negativeText = stringResource(id = negativeText),
|
||||
onNegativeClicked = onNegativeClicked,
|
||||
positiveText = stringResource(id = positiveText),
|
||||
onPositiveClicked = onPositiveClicked,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
negativeText: String,
|
||||
onNegativeClicked: () -> Unit,
|
||||
positiveText: String,
|
||||
onPositiveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().height(64.dp),
|
||||
modifier = modifier.fillMaxWidth().padding(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(modifier = Modifier.height(48.dp).weight(1f), onClick = onNegativeClicked) {
|
||||
Text(text = stringResource(id = negativeText))
|
||||
Text(text = negativeText)
|
||||
}
|
||||
OutlinedButton(modifier = Modifier.height(48.dp).weight(1f), enabled = enabled, onClick = onPositiveClicked) {
|
||||
Text(text = stringResource(id = positiveText))
|
||||
Button(modifier = Modifier.height(48.dp).weight(1f), enabled = enabled, onClick = onPositiveClicked) {
|
||||
Text(text = positiveText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,5 +79,11 @@ fun PreferenceFooter(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreferenceFooterPreview() {
|
||||
PreferenceFooter(enabled = true, onCancelClicked = {}, onSaveClicked = {})
|
||||
PreferenceFooter(
|
||||
enabled = true,
|
||||
negativeText = "Cancel",
|
||||
onNegativeClicked = {},
|
||||
positiveText = "Save",
|
||||
onPositiveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ protobuf = "4.32.1"
|
|||
activity = { module = "androidx.activity:activity" }
|
||||
activity-compose = { module = "androidx.activity:activity-compose" }
|
||||
actvity-ktx = { module = "androidx.activity:activity-ktx" }
|
||||
annotation = { module = "androidx.annotation:annotation", version = "1.9.1" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||
appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "appcompat" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
|
||||
|
|
Ładowanie…
Reference in New Issue