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