Feat/2412 ignored nodes (#2470)

Signed-off-by: DaneEvans <dane@goneepic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
DaneEvans 2025-07-21 22:45:03 +10:00 committed by GitHub
parent ee99d79574
commit 5a6a1cb44a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 40 deletions

View file

@ -160,6 +160,7 @@ data class NodesUiState(
val distanceUnits: Int = 0,
val tempInFahrenheit: Boolean = false,
val showDetails: Boolean = false,
val showIgnored: Boolean = false,
) {
companion object {
val Empty = NodesUiState()
@ -310,6 +311,12 @@ class UIViewModel @Inject constructor(
private val showPrecisionCircleOnMap =
MutableStateFlow(preferences.getBoolean("show-precision-circle-on-map", true))
private val showIgnored = MutableStateFlow(preferences.getBoolean("show-ignored", false))
fun toggleShowIgnored() {
showIgnored.value = !showIgnored.value
preferences.edit { putBoolean("show-ignored", showIgnored.value) }
}
fun setSortOption(sort: NodeSortOption) {
nodeSortOption.value = sort
preferences.edit { putInt("node-sort-option", sort.ordinal) }
@ -355,6 +362,7 @@ class UIViewModel @Inject constructor(
val includeUnknown: Boolean,
val onlyOnline: Boolean,
val onlyDirect: Boolean,
val showIgnored: Boolean,
)
val nodeFilterStateFlow: Flow<NodeFilterState> = combine(
@ -362,8 +370,9 @@ class UIViewModel @Inject constructor(
includeUnknown,
onlyOnline,
onlyDirect,
) { filterText, includeUnknown, onlyOnline, onlyDirect ->
NodeFilterState(filterText, includeUnknown, onlyOnline, onlyDirect)
showIgnored,
) { filterText, includeUnknown, onlyOnline, onlyDirect, showIgnored ->
NodeFilterState(filterText, includeUnknown, onlyOnline, onlyDirect, showIgnored)
}
val nodesUiState: StateFlow<NodesUiState> = combine(
@ -382,6 +391,7 @@ class UIViewModel @Inject constructor(
distanceUnits = profile.config.display.units.number,
tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit,
showDetails = showDetails,
showIgnored = filterFlow.showIgnored,
)
}.stateIn(
scope = viewModelScope,
@ -397,6 +407,9 @@ class UIViewModel @Inject constructor(
val nodeList: StateFlow<List<Node>> = nodesUiState.flatMapLatest { state ->
nodeDB.getNodes(state.sort, state.filter, state.includeUnknown, state.onlyOnline, state.onlyDirect)
.map { list ->
list.filter { it.isIgnored == state.showIgnored }
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),

View file

@ -65,6 +65,8 @@ fun NodeScreen(
val nodes by model.nodeList.collectAsStateWithLifecycle()
val ourNode by model.ourNodeInfo.collectAsStateWithLifecycle()
val unfilteredNodes by model.unfilteredNodeList.collectAsStateWithLifecycle()
val ignoredNodeCount = unfilteredNodes.count { it.isIgnored }
val listState = rememberLazyListState()
@ -114,6 +116,9 @@ fun NodeScreen(
onToggleOnlyDirect = model::toggleOnlyDirect,
showDetails = state.showDetails,
onToggleShowDetails = model::toggleShowDetails,
showIgnored = state.showIgnored,
onToggleShowIgnored = model::toggleShowIgnored,
ignoredNodeCount = ignoredNodeCount,
)
}

View file

@ -21,8 +21,10 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
@ -75,29 +77,54 @@ fun NodeFilterTextField(
onToggleOnlyDirect: () -> Unit,
showDetails: Boolean,
onToggleShowDetails: () -> Unit,
showIgnored: Boolean,
onToggleShowIgnored: () -> Unit,
ignoredNodeCount: Int,
) {
Row(
modifier = modifier.background(MaterialTheme.colorScheme.background),
) {
NodeFilterTextField(
filterText = filterText,
onTextChange = onTextChange,
modifier = Modifier.weight(1f)
)
Column(modifier = modifier.background(MaterialTheme.colorScheme.background)) {
Row {
NodeFilterTextField(
filterText = filterText,
onTextChange = onTextChange,
modifier = Modifier.weight(1f)
)
NodeSortButton(
modifier = Modifier.align(Alignment.CenterVertically),
currentSortOption = currentSortOption,
onSortSelect = onSortSelect,
includeUnknown = includeUnknown,
onToggleIncludeUnknown = onToggleIncludeUnknown,
onlyOnline = onlyOnline,
onToggleOnlyOnline = onToggleOnlyOnline,
onlyDirect = onlyDirect,
onToggleOnlyDirect = onToggleOnlyDirect,
showDetails = showDetails,
onToggleShowDetails = onToggleShowDetails,
)
NodeSortButton(
modifier = Modifier.align(Alignment.CenterVertically),
currentSortOption = currentSortOption,
onSortSelect = onSortSelect,
toggles = NodeFilterToggles(
includeUnknown = includeUnknown,
onToggleIncludeUnknown = onToggleIncludeUnknown,
onlyOnline = onlyOnline,
onToggleOnlyOnline = onToggleOnlyOnline,
onlyDirect = onlyDirect,
onToggleOnlyDirect = onToggleOnlyDirect,
showDetails = showDetails,
onToggleShowDetails = onToggleShowDetails,
showIgnored = showIgnored,
onToggleShowIgnored = onToggleShowIgnored,
ignoredNodeCount = ignoredNodeCount,
),
)
}
if (showIgnored) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceDim)
.clickable { onToggleShowIgnored() }
.padding(vertical = 16.dp, horizontal = 24.dp)
) {
Text(
text = stringResource(id = R.string.node_filter_ignored),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.fillMaxWidth(),
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
)
}
}
}
}
@ -159,14 +186,7 @@ private fun NodeFilterTextField(
private fun NodeSortButton(
currentSortOption: NodeSortOption,
onSortSelect: (NodeSortOption) -> Unit,
includeUnknown: Boolean,
onToggleIncludeUnknown: () -> Unit,
onlyOnline: Boolean,
onToggleOnlyOnline: () -> Unit,
onlyDirect: Boolean,
onToggleOnlyDirect: () -> Unit,
showDetails: Boolean,
onToggleShowDetails: () -> Unit,
toggles: NodeFilterToggles,
modifier: Modifier = Modifier,
) = Box(modifier) {
var expanded by remember { mutableStateOf(false) }
@ -202,12 +222,12 @@ private fun NodeSortButton(
HorizontalDivider()
DropdownMenuItem(
onClick = {
onToggleIncludeUnknown()
toggles.onToggleIncludeUnknown()
expanded = false
},
text = {
Row {
AnimatedVisibility(visible = includeUnknown) {
AnimatedVisibility(visible = toggles.includeUnknown) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
@ -222,12 +242,12 @@ private fun NodeSortButton(
)
DropdownMenuItem(
onClick = {
onToggleOnlyOnline()
toggles.onToggleOnlyOnline()
expanded = false
},
text = {
Row {
AnimatedVisibility(visible = onlyOnline) {
AnimatedVisibility(visible = toggles.onlyOnline) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
@ -242,12 +262,12 @@ private fun NodeSortButton(
)
DropdownMenuItem(
onClick = {
onToggleOnlyDirect()
toggles.onToggleOnlyDirect()
expanded = false
},
text = {
Row {
AnimatedVisibility(visible = onlyDirect) {
AnimatedVisibility(visible = toggles.onlyDirect) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
@ -263,12 +283,12 @@ private fun NodeSortButton(
HorizontalDivider()
DropdownMenuItem(
onClick = {
onToggleShowDetails()
toggles.onToggleShowDetails()
expanded = false
},
text = {
Row {
AnimatedVisibility(visible = showDetails) {
AnimatedVisibility(visible = toggles.showDetails) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
@ -281,6 +301,34 @@ private fun NodeSortButton(
}
}
)
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),
)
}
}
}
)
}
}
@ -302,6 +350,23 @@ private fun NodeFilterTextFieldPreview() {
onToggleOnlyDirect = {},
showDetails = false,
onToggleShowDetails = {},
showIgnored = false,
onToggleShowIgnored = {},
ignoredNodeCount = 0,
)
}
}
data class NodeFilterToggles(
val includeUnknown: Boolean,
val onToggleIncludeUnknown: () -> Unit,
val onlyOnline: Boolean,
val onToggleOnlyOnline: () -> Unit,
val onlyDirect: Boolean,
val onToggleOnlyDirect: () -> Unit,
val showDetails: Boolean,
val onToggleShowDetails: () -> Unit,
val showIgnored: Boolean,
val onToggleShowIgnored: () -> Unit,
val ignoredNodeCount: Int,
)