Spruce up `LoRaConfigScreen` (#3224)

pull/3230/head
Phil Oliver 2025-09-28 12:52:42 -04:00 zatwierdzone przez GitHub
rodzic 8c16052229
commit 3951ebb375
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
12 zmienionych plików z 332 dodań i 239 usunięć

Wyświetl plik

@ -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: () -&gt; 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 -&gt; when (action) { is NodeMenuAction.MoreDetails -&gt; { navController.navigate( NodesRoutes.NodeDetailGraph(action.node.num), { launchSingleTop = true restoreState = true }, ) } is NodeMenuAction.Share -&gt; sharedContact = action.node else -&gt; {} } }, )</ID>
<ID>ViewModelForwarding:Main.kt$ScannedQrCodeDialog(uIViewModel, newChannelSet)</ID>
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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) -&gt; Unit, modifier: Modifier = Modifier, summary: String? = null, maxSize: Int = 0, // max_size - 1 (in bytes) onFocusChanged: (FocusState) -&gt; Unit = {}, trailingIcon: (@Composable () -&gt; 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>

Wyświetl plik

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

Wyświetl plik

@ -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 = {},
)
}

Wyświetl plik

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