From cf59033c49177eeb27d48b68b674756065223dc8 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:47:06 -0400 Subject: [PATCH] Clearer node filter options (#3250) --- .../ui/node/components/NodeFilterTextField.kt | 169 +++++++++--------- core/strings/src/main/res/values/strings.xml | 2 + 2 files changed, 84 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeFilterTextField.kt b/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeFilterTextField.kt index 17a0ae966..4d6752411 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeFilterTextField.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/components/NodeFilterTextField.kt @@ -17,7 +17,6 @@ package com.geeksville.mesh.ui.node.components -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -25,8 +24,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons @@ -34,13 +35,16 @@ import androidx.compose.material.icons.automirrored.filled.Sort import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -54,6 +58,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import com.geeksville.mesh.ui.common.preview.LargeFontPreview @@ -184,104 +189,94 @@ private fun NodeSortButton( onDismissRequest = { expanded = false }, modifier = Modifier.background(MaterialTheme.colorScheme.background.copy(alpha = 1f)), ) { + DropdownMenuTitle(text = stringResource(R.string.node_sort_title)) + NodeSortOption.entries.forEach { sort -> - DropdownMenuItem( - onClick = { - onSortSelect(sort) - expanded = false - }, - text = { - Text( - text = stringResource(id = sort.stringRes), - fontWeight = if (sort == currentSortOption) FontWeight.ExtraBold else null, - ) - }, + DropdownMenuRadio( + text = stringResource(id = sort.stringRes), + selected = sort == currentSortOption, + onClick = { onSortSelect(sort) }, ) } - HorizontalDivider() - DropdownMenuItem( - onClick = { - toggles.onToggleIncludeUnknown() - expanded = false - }, - text = { - Row { - AnimatedVisibility(visible = toggles.includeUnknown) { - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - modifier = Modifier.padding(end = 4.dp), - ) - } - Text(text = stringResource(id = R.string.node_filter_include_unknown)) - } - }, + + HorizontalDivider(modifier = Modifier.padding(MenuDefaults.DropdownMenuItemContentPadding)) + + DropdownMenuTitle(text = stringResource(R.string.node_filter_title)) + + DropdownMenuCheck( + text = stringResource(R.string.node_filter_include_unknown), + checked = toggles.includeUnknown, + onClick = toggles.onToggleIncludeUnknown, ) - DropdownMenuItem( - onClick = { - toggles.onToggleOnlyOnline() - expanded = false - }, - text = { - Row { - AnimatedVisibility(visible = toggles.onlyOnline) { - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - modifier = Modifier.padding(end = 4.dp), - ) - } - Text(text = stringResource(id = R.string.node_filter_only_online)) - } - }, + + DropdownMenuCheck( + text = stringResource(R.string.node_filter_only_online), + checked = toggles.onlyOnline, + onClick = toggles.onToggleOnlyOnline, ) - DropdownMenuItem( - onClick = { - toggles.onToggleOnlyDirect() - expanded = false - }, - text = { - Row { - AnimatedVisibility(visible = toggles.onlyDirect) { - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - modifier = Modifier.padding(end = 4.dp), - ) - } - Text(text = stringResource(id = R.string.node_filter_only_direct)) - } - }, + + DropdownMenuCheck( + text = stringResource(R.string.node_filter_only_direct), + checked = toggles.onlyDirect, + onClick = toggles.onToggleOnlyDirect, ) - HorizontalDivider() - DropdownMenuItem( - onClick = { - toggles.onToggleShowIgnored() - expanded = false - }, - text = { - Row { - AnimatedVisibility(visible = toggles.showIgnored) { - Icon( - imageVector = Icons.Default.Done, - contentDescription = null, - modifier = Modifier.padding(end = 4.dp), - ) - } - Text(text = stringResource(id = R.string.node_filter_show_ignored)) - if (toggles.ignoredNodeCount > 0) { - Text( - text = " (${toggles.ignoredNodeCount})", - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(start = 4.dp), - ) - } + + DropdownMenuCheck( + text = stringResource(R.string.node_filter_show_ignored), + checked = toggles.showIgnored, + onClick = toggles.onToggleShowIgnored, + trailing = + if (toggles.ignoredNodeCount > 0) { + { + Text( + text = " (${toggles.ignoredNodeCount})", + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(start = 4.dp), + ) } + } else { + null }, ) } } +@Composable +private fun DropdownMenuTitle(text: String) { + Text( + text = text, + modifier = + Modifier.height(48.dp) + .padding(MenuDefaults.DropdownMenuItemContentPadding) + .wrapContentHeight(align = Alignment.CenterVertically), + fontWeight = FontWeight.ExtraBold, + ) +} + +@Composable +private fun DropdownMenuRadio(text: String, selected: Boolean, onClick: () -> Unit) { + DropdownMenuItem( + onClick = onClick, + leadingIcon = { RadioButton(selected = selected, onClick = null) }, + text = { Text(text = text) }, + ) +} + +@Composable +private fun DropdownMenuCheck( + text: String, + checked: Boolean, + onClick: () -> Unit, + trailing: @Composable (() -> Unit)? = null, +) { + DropdownMenuItem( + onClick = onClick, + leadingIcon = { Checkbox(checked = checked, onCheckedChange = null) }, + trailingIcon = trailing, + text = { Text(text = text) }, + ) +} + @PreviewLightDark @LargeFontPreview @Composable diff --git a/core/strings/src/main/res/values/strings.xml b/core/strings/src/main/res/values/strings.xml index cbbe4a4bd..b528ca8be 100644 --- a/core/strings/src/main/res/values/strings.xml +++ b/core/strings/src/main/res/values/strings.xml @@ -31,11 +31,13 @@ Meshtastic %s Filter clear node filter + Filter by Include unknown Hide offline nodes Only show direct nodes You are viewing ignored nodes,\nPress to return to the node list. Show details + Sort by Node sorting options A-Z Channel