fix: Multiple bugs - settings text fields, dropdowns, missing override duty cycle, and MQTT icon display (#3833)

This commit is contained in:
Mac DeCourcy 2025-11-26 17:45:09 -08:00 committed by GitHub
parent 9bc1b87e75
commit 1c3784235e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 58 additions and 55 deletions

View file

@ -721,6 +721,7 @@ class MeshService : Service() {
rssi = packet.rxRssi,
replyId = data.replyId,
relayNode = packet.relayNode,
viaMqtt = packet.viaMqtt,
)
}

View file

@ -52,7 +52,7 @@ data class PacketEntity(
packetId = packetId,
emojis = reactions.toReaction(getNode),
replyId = data.replyId,
viaMqtt = node.viaMqtt,
viaMqtt = data.viaMqtt,
relayNode = data.relayNode,
relays = data.relays,
)

View file

@ -63,6 +63,7 @@ data class DataPacket(
var replyId: Int? = null, // If this is a reply to a previous message, this is the ID of that message
var relayNode: Int? = null,
var relays: Int = 0,
var viaMqtt: Boolean = false, // True if this packet passed via MQTT somewhere along its path
) : Parcelable {
/** If there was an error with this message, this string describes what was wrong. */

View file

@ -163,6 +163,7 @@ fun EditTextPreference(
onValueChanged: (Double) -> Unit,
modifier: Modifier = Modifier,
summary: String? = null,
onFocusChanged: (FocusState) -> Unit = {},
) {
var valueState by remember(value) { mutableStateOf(value.toString()) }
val decimalSeparators = setOf('.', ',', '٫', '、', '·') // set of possible decimal separators
@ -185,7 +186,7 @@ fun EditTextPreference(
}
}
},
onFocusChanged = {},
onFocusChanged = onFocusChanged,
modifier = modifier,
)
}

View file

@ -72,6 +72,8 @@ fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(),
enabled = state.connected,
responseState = state.responseState,
onDismissPacketResponse = viewModel::clearPacketResponse,
additionalDirtyCheck = { messagesInput != messages },
onDiscard = { messagesInput = messages },
onSave = {
if (messagesInput != messages) {
viewModel.setCannedMessages(messagesInput)

View file

@ -130,6 +130,8 @@ fun ExternalNotificationConfigScreen(
enabled = state.connected,
responseState = state.responseState,
onDismissPacketResponse = viewModel::clearPacketResponse,
additionalDirtyCheck = { ringtoneInput != ringtone },
onDiscard = { ringtoneInput = ringtone },
onSave = {
if (ringtoneInput != ringtone) {
viewModel.setRingtone(ringtoneInput)
@ -259,7 +261,7 @@ fun ExternalNotificationConfigScreen(
DropDownPreference(
title = stringResource(Res.string.output_duration_milliseconds),
items = outputItems.map { it.value to it.toDisplayString() },
selectedItem = formState.value.outputMs,
selectedItem = formState.value.outputMs.toLong(),
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { outputMs = it.toInt() } },
)
@ -268,7 +270,7 @@ fun ExternalNotificationConfigScreen(
DropDownPreference(
title = stringResource(Res.string.nag_timeout_seconds),
items = nagItems.map { it.value to it.toDisplayString() },
selectedItem = formState.value.nagTimeout,
selectedItem = formState.value.nagTimeout.toLong(),
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { nagTimeout = it.toInt() } },
)

View file

@ -47,6 +47,7 @@ import org.meshtastic.core.strings.lora
import org.meshtastic.core.strings.modem_preset
import org.meshtastic.core.strings.ok_to_mqtt
import org.meshtastic.core.strings.options
import org.meshtastic.core.strings.override_duty_cycle
import org.meshtastic.core.strings.override_frequency_mhz
import org.meshtastic.core.strings.pa_fan_disabled
import org.meshtastic.core.strings.region_frequency_plan
@ -169,6 +170,14 @@ fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
containerColor = CardDefaults.cardColors().containerColor,
)
HorizontalDivider()
SwitchPreference(
title = stringResource(Res.string.override_duty_cycle),
checked = formState.value.overrideDutyCycle,
enabled = state.connected,
onCheckedChange = { formState.value = formState.value.copy { overrideDutyCycle = it } },
containerColor = CardDefaults.cardColors().containerColor,
)
HorizontalDivider()
val hopLimitItems = remember { hopLimits }
DropDownPreference(
title = stringResource(Res.string.hop_limit),

View file

@ -149,6 +149,8 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
enabled = state.connected,
responseState = state.responseState,
onDismissPacketResponse = viewModel::clearPacketResponse,
additionalDirtyCheck = { locationInput != currentPosition },
onDiscard = { locationInput = currentPosition },
onSave = {
if (formState.value.fixedPosition) {
if (locationInput != currentPosition) {
@ -233,9 +235,9 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
value = locationInput.latitude,
enabled = state.connected,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { value ->
if (value >= -90 && value <= 90.0) {
locationInput = locationInput.copy(latitude = value)
onValueChanged = { lat: Double ->
if (lat >= -90 && lat <= 90.0) {
locationInput = locationInput.copy(latitude = lat)
}
},
)
@ -245,9 +247,9 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
value = locationInput.longitude,
enabled = state.connected,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { value ->
if (value >= -180 && value <= 180.0) {
locationInput = locationInput.copy(longitude = value)
onValueChanged = { lon: Double ->
if (lon >= -180 && lon <= 180.0) {
locationInput = locationInput.copy(longitude = lon)
}
},
)
@ -257,7 +259,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
value = locationInput.altitude,
enabled = state.connected,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { value -> locationInput = locationInput.copy(altitude = value) },
onValueChanged = { alt: Int -> locationInput = locationInput.copy(altitude = alt) },
)
HorizontalDivider()
TextButton(

View file

@ -22,20 +22,14 @@ import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.dp
@ -59,6 +53,8 @@ fun <T : MessageLite> RadioConfigScreenList(
enabled: Boolean,
onSave: (T) -> Unit,
modifier: Modifier = Modifier,
additionalDirtyCheck: () -> Boolean = { false },
onDiscard: () -> Unit = {},
content: LazyListScope.() -> Unit,
) {
val focusManager = LocalFocusManager.current
@ -81,48 +77,37 @@ fun <T : MessageLite> RadioConfigScreenList(
)
},
) { innerPadding ->
val showFooterButtons = configState.isDirty
val showFooterButtons = configState.isDirty || additionalDirtyCheck()
Box(modifier = Modifier.padding(innerPadding)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
content()
LazyColumn(
modifier = Modifier.padding(innerPadding).fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
content()
item {
AnimatedVisibility(
visible = showFooterButtons,
modifier = Modifier.align(Alignment.BottomCenter),
enter = expandIn(),
exit = shrinkOut(),
) {
Spacer(modifier = Modifier.height(64.dp))
}
item {
AnimatedVisibility(
visible = showFooterButtons,
enter = fadeIn() + expandIn(),
exit = fadeOut() + shrinkOut(),
) {
PreferenceFooter(
enabled = enabled && showFooterButtons,
negativeText = stringResource(Res.string.discard_changes),
onNegativeClicked = {
focusManager.clearFocus()
configState.reset()
onDiscard()
},
positiveText = stringResource(Res.string.save_changes),
onPositiveClicked = {
focusManager.clearFocus()
onSave(configState.value)
},
)
}
}
AnimatedVisibility(
visible = showFooterButtons,
modifier = Modifier.align(Alignment.BottomCenter),
enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
exit = fadeOut() + slideOutVertically(targetOffsetY = { it }),
) {
PreferenceFooter(
enabled = enabled && configState.isDirty,
negativeText = stringResource(Res.string.discard_changes),
onNegativeClicked = {
focusManager.clearFocus()
configState.reset()
},
positiveText = stringResource(Res.string.save_changes),
onPositiveClicked = {
focusManager.clearFocus()
onSave(configState.value)
},
)
}
}
}
}