From dd50cf230e47a148ae9fbdd92b0cc4f6bcf7e93f Mon Sep 17 00:00:00 2001 From: DaneEvans Date: Fri, 27 Jun 2025 23:13:21 +1000 Subject: [PATCH] feat(debug): add a toggle to AND/OR all filters. (#2265) --- .../mesh/compose/DebugFiltersTest.kt | 11 +++++--- .../mesh/compose/DebugSearchTest.kt | 10 +++++-- .../com/geeksville/mesh/ui/debug/Debug.kt | 27 +++++++++++++------ .../geeksville/mesh/ui/debug/DebugFilters.kt | 19 +++++++++++++ .../geeksville/mesh/ui/debug/DebugSearch.kt | 10 ++++++- app/src/main/res/values/strings.xml | 4 ++- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/com/geeksville/mesh/compose/DebugFiltersTest.kt b/app/src/androidTest/java/com/geeksville/mesh/compose/DebugFiltersTest.kt index 71dc74269..357ec9f88 100644 --- a/app/src/androidTest/java/com/geeksville/mesh/compose/DebugFiltersTest.kt +++ b/app/src/androidTest/java/com/geeksville/mesh/compose/DebugFiltersTest.kt @@ -31,6 +31,7 @@ import org.junit.runner.RunWith import androidx.test.platform.app.InstrumentationRegistry import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue +import com.geeksville.mesh.ui.debug.FilterMode @RunWith(AndroidJUnit4::class) class DebugFiltersTest { @@ -67,13 +68,15 @@ class DebugFiltersTest { var customFilterText by androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf("") } com.geeksville.mesh.ui.debug.DebugActiveFilters( filterTexts = filterTexts, - onFilterTextsChange = { filterTexts = it } + onFilterTextsChange = { filterTexts = it }, + filterMode = FilterMode.OR, + onFilterModeChange = {} ) com.geeksville.mesh.ui.debug.DebugCustomFilterInput( customFilterText = customFilterText, onCustomFilterTextChange = { customFilterText = it }, filterTexts = filterTexts, - onFilterTextsChange = { filterTexts = it } + onFilterTextsChange = { filterTexts = it }, ) } // Add a custom filter @@ -92,7 +95,9 @@ class DebugFiltersTest { var filterTexts by androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(listOf("A", "B")) } com.geeksville.mesh.ui.debug.DebugActiveFilters( filterTexts = filterTexts, - onFilterTextsChange = { filterTexts = it } + onFilterTextsChange = { filterTexts = it }, + filterMode = FilterMode.OR, + onFilterModeChange = {} ) } // The active filters label and chips should be visible diff --git a/app/src/androidTest/java/com/geeksville/mesh/compose/DebugSearchTest.kt b/app/src/androidTest/java/com/geeksville/mesh/compose/DebugSearchTest.kt index 021312f83..aeea32257 100644 --- a/app/src/androidTest/java/com/geeksville/mesh/compose/DebugSearchTest.kt +++ b/app/src/androidTest/java/com/geeksville/mesh/compose/DebugSearchTest.kt @@ -37,6 +37,8 @@ import org.junit.runner.RunWith import androidx.test.platform.app.InstrumentationRegistry import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue +import com.geeksville.mesh.ui.debug.FilterMode +import com.geeksville.mesh.ui.debug.DebugActiveFilters @RunWith(AndroidJUnit4::class) class DebugSearchTest { @@ -137,7 +139,9 @@ class DebugSearchTest { var customFilterText by androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf("") } com.geeksville.mesh.ui.debug.DebugActiveFilters( filterTexts = filterTexts, - onFilterTextsChange = { filterTexts = it } + onFilterTextsChange = { filterTexts = it }, + filterMode = FilterMode.OR, + onFilterModeChange = {} ) com.geeksville.mesh.ui.debug.DebugCustomFilterInput( customFilterText = customFilterText, @@ -162,7 +166,9 @@ class DebugSearchTest { var filterTexts by androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(listOf("A", "B")) } com.geeksville.mesh.ui.debug.DebugActiveFilters( filterTexts = filterTexts, - onFilterTextsChange = { filterTexts = it } + onFilterTextsChange = { filterTexts = it }, + filterMode = FilterMode.OR, + onFilterModeChange = {} ) } // The active filters label and chips should be visible diff --git a/app/src/main/java/com/geeksville/mesh/ui/debug/Debug.kt b/app/src/main/java/com/geeksville/mesh/ui/debug/Debug.kt index a2d868a66..c80e17f75 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/debug/Debug.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/debug/Debug.kt @@ -108,12 +108,24 @@ internal fun DebugScreen( val filterTexts by viewModel.filterTexts.collectAsStateWithLifecycle() val selectedLogId by viewModel.selectedLogId.collectAsStateWithLifecycle() - val filteredLogs = remember(logs, filterTexts) { + var filterMode by remember { mutableStateOf(FilterMode.OR) } + + val filteredLogs = remember(logs, filterTexts, filterMode) { logs.filter { log -> - filterTexts.isEmpty() || filterTexts.any { filterText -> - log.logMessage.contains(filterText, ignoreCase = true) || - log.messageType.contains(filterText, ignoreCase = true) || - log.formattedReceivedDate.contains(filterText, ignoreCase = true) + if (filterTexts.isEmpty()) { + true + } else { when (filterMode) { + FilterMode.OR -> filterTexts.any { filterText -> + log.logMessage.contains(filterText, ignoreCase = true) || + log.messageType.contains(filterText, ignoreCase = true) || + log.formattedReceivedDate.contains(filterText, ignoreCase = true) + } + FilterMode.AND -> filterTexts.all { filterText -> + log.logMessage.contains(filterText, ignoreCase = true) || + log.messageType.contains(filterText, ignoreCase = true) || + log.formattedReceivedDate.contains(filterText, ignoreCase = true) + } + } } }.toImmutableList() } @@ -136,7 +148,6 @@ internal fun DebugScreen( listState.requestScrollToItem(searchState.allMatches[searchState.currentMatchIndex].logIndex) } } - Column( modifier = Modifier.fillMaxSize() ) { @@ -156,9 +167,10 @@ internal fun DebugScreen( searchState = searchState, filterTexts = filterTexts, presetFilters = viewModel.presetFilters, + filterMode = filterMode, + onFilterModeChange = { filterMode = it } ) } - items(filteredLogs, key = { it.uuid }) { log -> DebugItem( modifier = Modifier.animateItem(), @@ -708,7 +720,6 @@ fun DebugMenuActions( contentDescription = "Clear All" ) } - if (showDeleteLogsDialog) { SimpleAlertDialog( title = R.string.debug_clear, diff --git a/app/src/main/java/com/geeksville/mesh/ui/debug/DebugFilters.kt b/app/src/main/java/com/geeksville/mesh/ui/debug/DebugFilters.kt index cdadcc1a8..99225d29b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/debug/DebugFilters.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/debug/DebugFilters.kt @@ -215,10 +215,13 @@ internal fun DebugFilterBar( } } +@Suppress("LongMethod") @Composable internal fun DebugActiveFilters( filterTexts: List, onFilterTextsChange: (List) -> Unit, + filterMode: FilterMode, + onFilterModeChange: (FilterMode) -> Unit, modifier: Modifier = Modifier ) { val colorScheme = MaterialTheme.colorScheme @@ -237,6 +240,20 @@ internal fun DebugActiveFilters( text = stringResource(R.string.debug_active_filters), style = TextStyle(fontWeight = FontWeight.Bold) ) + TextButton( + onClick = { + onFilterModeChange( + if (filterMode == FilterMode.OR) FilterMode.AND else FilterMode.OR + ) + } + ) { + Text(if (filterMode == FilterMode.OR) { + stringResource(R.string.match_any) + } else { + stringResource(R.string.match_all) + } + ) + } IconButton( onClick = { onFilterTextsChange(emptyList()) } ) { @@ -278,3 +295,5 @@ internal fun DebugActiveFilters( } } } + +enum class FilterMode { OR, AND } diff --git a/app/src/main/java/com/geeksville/mesh/ui/debug/DebugSearch.kt b/app/src/main/java/com/geeksville/mesh/ui/debug/DebugSearch.kt index 22156de5c..93914ba3b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/debug/DebugSearch.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/debug/DebugSearch.kt @@ -163,6 +163,8 @@ internal fun DebugSearchState( onPreviousMatch: () -> Unit, onClearSearch: () -> Unit, onFilterTextsChange: (List) -> Unit, + filterMode: FilterMode, + onFilterModeChange: (FilterMode) -> Unit, ) { val colorScheme = MaterialTheme.colorScheme var customFilterText by remember { mutableStateOf("") } @@ -198,7 +200,9 @@ internal fun DebugSearchState( } DebugActiveFilters( filterTexts = filterTexts, - onFilterTextsChange = onFilterTextsChange + onFilterTextsChange = onFilterTextsChange, + filterMode = filterMode, + onFilterModeChange = onFilterModeChange ) } } @@ -209,6 +213,8 @@ fun DebugSearchStateviewModelDefaults( searchState: SearchState, filterTexts: List, presetFilters: List, + filterMode: FilterMode, + onFilterModeChange: (FilterMode) -> Unit, ) { val viewModel: DebugViewModel = hiltViewModel() DebugSearchState( @@ -221,6 +227,8 @@ fun DebugSearchStateviewModelDefaults( onPreviousMatch = viewModel.searchManager::goToPreviousMatch, onClearSearch = viewModel.searchManager::clearSearch, onFilterTextsChange = viewModel.filterManager::setFilterTexts, + filterMode = filterMode, + onFilterModeChange = onFilterModeChange ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa82444d..e1f76b781 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -165,9 +165,11 @@ Export Logs 500 last messages Filters - Active filters (Match any) + Active filters Search in logs… Clear Logs + Match Any | All + Match All | Any This will remove all log packets and database entries from your device - It is a full reset, and is permanent. Clear Updating firmware, wait up to eight minutes…