diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt index e1282071f..3e2866346 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt @@ -28,7 +28,7 @@ import com.geeksville.mesh.ui.sharing.ChannelScreen import org.meshtastic.core.navigation.ChannelsRoutes import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI import org.meshtastic.feature.settings.navigation.ConfigRoute -import org.meshtastic.feature.settings.radio.component.ChannelConfigScreen +import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen import org.meshtastic.feature.settings.radio.component.LoRaConfigScreen /** Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels]. */ diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelItem.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelItem.kt index 94a9a84d1..2d6365a11 100644 --- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelItem.kt +++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ChannelItem.kt @@ -31,7 +31,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import org.meshtastic.core.ui.theme.AppTheme @Composable fun ChannelItem( @@ -60,3 +62,9 @@ fun ChannelItem( } } } + +@Preview +@Composable +private fun ChannelItemPreview() { + AppTheme { ChannelItem(index = 0, title = "Medium Fast", enabled = true) {} } +} diff --git a/feature/settings/detekt-baseline.xml b/feature/settings/detekt-baseline.xml index 45086dd7c..8961b388d 100644 --- a/feature/settings/detekt-baseline.xml +++ b/feature/settings/detekt-baseline.xml @@ -2,7 +2,7 @@ - ComposableParamOrder:ChannelSettingsItemList.kt$ChannelSettingsItemList + ComposableParamOrder:ChannelConfigScreen.kt$ChannelConfigScreen ComposableParamOrder:Debug.kt$DecodedPayloadBlock ComposableParamOrder:DebugSearch.kt$DebugSearchState ComposableParamOrder:DebugSearch.kt$DebugSearchStateviewModelDefaults @@ -49,7 +49,7 @@ MultipleEmitters:CleanNodeDatabaseScreen.kt$NodesDeletionPreview MultipleEmitters:RadioConfig.kt$RadioConfigItemList NestedBlockDepth:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket) - ParameterNaming:ChannelSettingsItemList.kt$onPositiveClicked + ParameterNaming:ChannelConfigScreen.kt$onPositiveClicked ParameterNaming:CleanNodeDatabaseScreen.kt$onCheckedChanged ParameterNaming:CleanNodeDatabaseScreen.kt$onDaysChanged ParameterNaming:MapReportingPreference.kt$onMapReportingEnabledChanged @@ -61,8 +61,8 @@ TooGenericExceptionCaught:LanguageUtils.kt$LanguageUtils$e: Exception TooGenericExceptionCaught:RadioConfigViewModel.kt$RadioConfigViewModel$ex: Exception TooManyFunctions:RadioConfigViewModel.kt$RadioConfigViewModel : ViewModel - UnusedParameter:ChannelSettingsItemList.kt$onBack: () -> Unit - UnusedParameter:ChannelSettingsItemList.kt$title: String + UnusedParameter:ChannelConfigScreen.kt$onBack: () -> Unit + UnusedParameter:ChannelConfigScreen.kt$title: String ViewModelInjection:DebugSearch.kt$viewModel diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt index e0cd6acad..f7d2d6530 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt @@ -32,11 +32,11 @@ import org.meshtastic.feature.settings.SettingsScreen import org.meshtastic.feature.settings.debugging.DebugScreen import org.meshtastic.feature.settings.radio.CleanNodeDatabaseScreen import org.meshtastic.feature.settings.radio.RadioConfigViewModel +import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen import org.meshtastic.feature.settings.radio.component.AmbientLightingConfigScreen import org.meshtastic.feature.settings.radio.component.AudioConfigScreen import org.meshtastic.feature.settings.radio.component.BluetoothConfigScreen import org.meshtastic.feature.settings.radio.component.CannedMessageConfigScreen -import org.meshtastic.feature.settings.radio.component.ChannelConfigScreen import org.meshtastic.feature.settings.radio.component.DetectionSensorConfigScreen import org.meshtastic.feature.settings.radio.component.DeviceConfigScreen import org.meshtastic.feature.settings.radio.component.DisplayConfigScreen diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelSettingsItemList.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelConfigScreen.kt similarity index 80% rename from feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelSettingsItemList.kt rename to feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelConfigScreen.kt index fd89dc383..b06c42751 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelSettingsItemList.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/ChannelConfigScreen.kt @@ -15,32 +15,25 @@ * along with this program. If not, see . */ -package org.meshtastic.feature.settings.radio.component +package org.meshtastic.feature.settings.radio.channel import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally -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.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Add -import androidx.compose.material.icons.twotone.Close import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -63,61 +56,22 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.meshtastic.core.model.Channel import org.meshtastic.core.model.DeviceVersion import org.meshtastic.core.strings.R -import org.meshtastic.core.ui.component.ChannelItem -import org.meshtastic.core.ui.component.PreferenceCategory import org.meshtastic.core.ui.component.PreferenceFooter -import org.meshtastic.core.ui.component.SecurityIcon import org.meshtastic.core.ui.component.dragContainer import org.meshtastic.core.ui.component.dragDropItemsIndexed import org.meshtastic.core.ui.component.rememberDragDropState import org.meshtastic.feature.settings.radio.RadioConfigViewModel +import org.meshtastic.feature.settings.radio.channel.component.ChannelCard +import org.meshtastic.feature.settings.radio.channel.component.ChannelConfigHeader +import org.meshtastic.feature.settings.radio.channel.component.ChannelLegend +import org.meshtastic.feature.settings.radio.channel.component.ChannelLegendDialog +import org.meshtastic.feature.settings.radio.channel.component.EditChannelDialog +import org.meshtastic.feature.settings.radio.channel.component.SECONDARY_CHANNEL_EPOCH +import org.meshtastic.feature.settings.radio.component.PacketResponseStateDialog import org.meshtastic.proto.ChannelProtos.ChannelSettings import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig import org.meshtastic.proto.channelSettings -@Composable -private fun ChannelCard( - index: Int, - title: String, - enabled: Boolean, - channelSettings: ChannelSettings, - loraConfig: LoRaConfig, - onEditClick: () -> Unit, - onDeleteClick: () -> Unit, - sharesLocation: Boolean, -) = ChannelItem(index = index, title = title, enabled = enabled, onClick = onEditClick) { - if (sharesLocation) { - Icon( - imageVector = ChannelIcons.LOCATION.icon, - contentDescription = stringResource(ChannelIcons.LOCATION.descriptionResId), - modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), - ) - } - if (channelSettings.uplinkEnabled) { - Icon( - imageVector = ChannelIcons.UPLINK.icon, - contentDescription = stringResource(ChannelIcons.UPLINK.descriptionResId), - modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), - ) - } - if (channelSettings.downlinkEnabled) { - Icon( - imageVector = ChannelIcons.DOWNLINK.icon, - contentDescription = stringResource(ChannelIcons.DOWNLINK.descriptionResId), - modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), - ) - } - SecurityIcon(channelSettings, loraConfig) - Spacer(modifier = Modifier.width(10.dp)) - IconButton(onClick = { onDeleteClick() }) { - Icon( - imageVector = Icons.TwoTone.Close, - contentDescription = stringResource(R.string.delete), - modifier = Modifier.wrapContentSize(), - ) - } -} - @Composable fun ChannelConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() @@ -126,7 +80,7 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse) } - ChannelSettingsItemList( + ChannelConfigScreen( title = stringResource(id = R.string.channels), onBack = onBack, settingsList = state.channelList, @@ -140,7 +94,7 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) { @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable -private fun ChannelSettingsItemList( +private fun ChannelConfigScreen( title: String, onBack: () -> Unit, settingsList: List, @@ -217,7 +171,7 @@ private fun ChannelSettingsItemList( ) { innerPadding -> Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) { Column { - ChannelsConfigHeader( + ChannelConfigHeader( frequency = if (loraConfig.overrideFrequency != 0f) { loraConfig.overrideFrequency @@ -267,13 +221,13 @@ private fun ChannelSettingsItemList( item { PreferenceFooter( enabled = enabled && isEditing, - negativeText = R.string.cancel, + negativeText = stringResource(R.string.cancel), onNegativeClicked = { focusManager.clearFocus() settingsListInput.clear() settingsListInput.addAll(settingsList) }, - positiveText = R.string.send, + positiveText = stringResource(R.string.send), onPositiveClicked = { focusManager.clearFocus() onPositiveClicked(settingsListInput) @@ -301,21 +255,6 @@ private fun ChannelSettingsItemList( } } -@Composable -private fun ChannelsConfigHeader(frequency: Float, slot: Int) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - 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) - } - } -} - /** * Determines what [Channel] if any is enabled to conduct automatic location sharing. * @@ -345,8 +284,8 @@ private fun determineLocationSharingChannel(firmwareVersion: DeviceVersion, sett @Preview(showBackground = true) @Composable -private fun ChannelSettingsPreview() { - ChannelSettingsItemList( +private fun ChannelConfigScreenPreview() { + ChannelConfigScreen( title = "Channels", onBack = {}, settingsList = diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelCard.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelCard.kt new file mode 100644 index 000000000..32a31a266 --- /dev/null +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelCard.kt @@ -0,0 +1,104 @@ +/* + * 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 . + */ + +package org.meshtastic.feature.settings.radio.channel.component + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.twotone.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +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 +import org.meshtastic.core.ui.component.ChannelItem +import org.meshtastic.core.ui.component.SecurityIcon +import org.meshtastic.core.ui.theme.AppTheme +import org.meshtastic.proto.ChannelProtos.ChannelSettings +import org.meshtastic.proto.ConfigKt.loRaConfig +import org.meshtastic.proto.ConfigProtos.Config.LoRaConfig +import org.meshtastic.proto.channelSettings + +@Composable +internal fun ChannelCard( + index: Int, + title: String, + enabled: Boolean, + channelSettings: ChannelSettings, + loraConfig: LoRaConfig, + onEditClick: () -> Unit, + onDeleteClick: () -> Unit, + sharesLocation: Boolean, +) = ChannelItem(index = index, title = title, enabled = enabled, onClick = onEditClick) { + if (sharesLocation) { + Icon( + imageVector = ChannelIcons.LOCATION.icon, + contentDescription = stringResource(ChannelIcons.LOCATION.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + if (channelSettings.uplinkEnabled) { + Icon( + imageVector = ChannelIcons.UPLINK.icon, + contentDescription = stringResource(ChannelIcons.UPLINK.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + if (channelSettings.downlinkEnabled) { + Icon( + imageVector = ChannelIcons.DOWNLINK.icon, + contentDescription = stringResource(ChannelIcons.DOWNLINK.descriptionResId), + modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp), + ) + } + SecurityIcon(channelSettings, loraConfig) + Spacer(modifier = Modifier.width(10.dp)) + IconButton(onClick = { onDeleteClick() }) { + Icon( + imageVector = Icons.TwoTone.Close, + contentDescription = stringResource(R.string.delete), + modifier = Modifier.wrapContentSize(), + ) + } +} + +@Preview +@Composable +private fun ChannelCardPreview() { + AppTheme { + ChannelCard( + index = 0, + title = "Medium Fast", + enabled = true, + channelSettings = + channelSettings { + uplinkEnabled = true + downlinkEnabled = true + }, + loraConfig = loRaConfig {}, + onEditClick = {}, + onDeleteClick = {}, + sharesLocation = true, + ) + } +} diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelConfigHeader.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelConfigHeader.kt new file mode 100644 index 000000000..0f7346b9a --- /dev/null +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelConfigHeader.kt @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +package org.meshtastic.feature.settings.radio.channel.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import org.meshtastic.core.strings.R +import org.meshtastic.core.ui.component.PreferenceCategory +import org.meshtastic.core.ui.theme.AppTheme + +@Composable +internal fun ChannelConfigHeader(frequency: Float, slot: Int) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + 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 +@Composable +private fun ChannelConfigHeaderPreview() { + AppTheme { ChannelConfigHeader(frequency = 913.125f, slot = 45) } +} diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelLegend.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelLegend.kt similarity index 99% rename from feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelLegend.kt rename to feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelLegend.kt index 8c8751d86..1c900420d 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ChannelLegend.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/ChannelLegend.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.meshtastic.feature.settings.radio.component +package org.meshtastic.feature.settings.radio.channel.component import androidx.annotation.StringRes import androidx.compose.foundation.clickable diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/EditChannelDialog.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/EditChannelDialog.kt similarity index 99% rename from feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/EditChannelDialog.kt rename to feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/EditChannelDialog.kt index 6ea4d8ffb..17d439ce5 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/EditChannelDialog.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/channel/component/EditChannelDialog.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.meshtastic.feature.settings.radio.component +package org.meshtastic.feature.settings.radio.channel.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column