mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-06 13:45:06 -04:00
feat: Channel Configuration ui (#1873)
This commit is contained in:
@@ -23,12 +23,16 @@ import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -47,6 +51,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -59,9 +64,11 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.model.Channel
|
||||
@@ -165,7 +172,7 @@ fun ChannelConfigScreen(
|
||||
|
||||
ChannelSettingsItemList(
|
||||
settingsList = state.channelList,
|
||||
modemPresetName = Channel(loraConfig = state.radioConfig.lora).name,
|
||||
loraConfig = state.radioConfig.lora,
|
||||
enabled = state.connected,
|
||||
maxChannels = viewModel.maxChannels,
|
||||
onPositiveClicked = { channelListInput ->
|
||||
@@ -174,16 +181,21 @@ fun ChannelConfigScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun ChannelSettingsItemList(
|
||||
settingsList: List<ChannelSettings>,
|
||||
modemPresetName: String = stringResource(R.string.default_),
|
||||
loraConfig: LoRaConfig,
|
||||
maxChannels: Int = 8,
|
||||
enabled: Boolean,
|
||||
onNegativeClicked: () -> Unit = { },
|
||||
onPositiveClicked: (List<ChannelSettings>) -> Unit,
|
||||
) {
|
||||
val primarySettings = settingsList.getOrNull(0) ?: return
|
||||
val primaryChannel by remember(loraConfig) {
|
||||
mutableStateOf(Channel(primarySettings, loraConfig))
|
||||
}
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
val settingsListInput = rememberSaveable(
|
||||
saver = listSaver(save = { it.toList() }, restore = { it.toMutableStateList() })
|
||||
@@ -207,7 +219,7 @@ fun ChannelSettingsItemList(
|
||||
channelSettings = with(settingsListInput) {
|
||||
if (size > index) get(index) else channelSettings { }
|
||||
},
|
||||
modemPresetName = modemPresetName,
|
||||
modemPresetName = primaryChannel.name,
|
||||
onAddClick = {
|
||||
if (settingsListInput.size > index) {
|
||||
settingsListInput[index] = it
|
||||
@@ -225,45 +237,94 @@ fun ChannelSettingsItemList(
|
||||
.fillMaxSize()
|
||||
.clickable(onClick = { }, enabled = false)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
haptics = LocalHapticFeedback.current,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.channels)) }
|
||||
Column {
|
||||
|
||||
dragDropItemsIndexed(
|
||||
items = settingsListInput,
|
||||
dragDropState = dragDropState,
|
||||
) { index, channel, isDragging ->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
onNegativeClicked()
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
ChannelsConfigHeader(
|
||||
frequency = if (loraConfig.overrideFrequency != 0f) {
|
||||
loraConfig.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
slot = if (loraConfig.channelNum != 0) {
|
||||
loraConfig.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.press_and_drag),
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
haptics = LocalHapticFeedback.current,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(R.string.primary),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
dragDropItemsIndexed(
|
||||
items = settingsListInput,
|
||||
dragDropState = dragDropState,
|
||||
) { index, channel, isDragging ->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { primaryChannel.name },
|
||||
enabled = enabled,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) }
|
||||
)
|
||||
if (index == 0 && !isDragging) {
|
||||
Text(
|
||||
text = stringResource(R.string.primary_channel_feature),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.secondary),
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.secondary_no_telemetry),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.manuel_position_request),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
onNegativeClicked()
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +357,29 @@ fun ChannelSettingsItemList(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelsConfigHeader(
|
||||
frequency: Float,
|
||||
slot: Int
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
PreferenceCategory(text = stringResource(R.string.channels))
|
||||
Column {
|
||||
Text(
|
||||
text = "${stringResource(R.string.freq)}: ${frequency}MHz",
|
||||
fontSize = 11.sp,
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(R.string.slot)}: $slot",
|
||||
fontSize = 11.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ChannelSettingsPreview() {
|
||||
@@ -309,6 +393,7 @@ private fun ChannelSettingsPreview() {
|
||||
name = stringResource(R.string.channel_name)
|
||||
},
|
||||
),
|
||||
loraConfig = Channel.default.loraConfig,
|
||||
enabled = true,
|
||||
onPositiveClicked = { },
|
||||
)
|
||||
|
||||
@@ -607,6 +607,14 @@
|
||||
<string name="heading">Heading</string>
|
||||
<string name="sats">Sats</string>
|
||||
<string name="alt">Alt</string>
|
||||
<string name="freq">Freq</string>
|
||||
<string name="slot">Slot</string>
|
||||
<string name="primary">Primary</string>
|
||||
<string name="primary_channel_feature">Periodic position and telemetry broadcast</string>
|
||||
<string name="secondary">Secondary</string>
|
||||
<string name="secondary_no_telemetry">No periodic telemetry broadcast</string>
|
||||
<string name="manuel_position_request">Manual position request required</string>
|
||||
<string name="press_and_drag">Press and drag to reorder</string>
|
||||
<string name="set_region">Set Region</string>
|
||||
<string name="unmute">Unmute</string>
|
||||
<string name="dynamic">Dynamic</string>
|
||||
|
||||
Reference in New Issue
Block a user