kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat(map): add last heard filter for map nodes (#3219)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>pull/3222/head
rodzic
ab18e424b1
commit
61c6d6c76e
|
@ -301,11 +301,13 @@ fun MapView(
|
|||
val displayableWaypoints = waypoints.values.mapNotNull { it.data.waypoint }
|
||||
|
||||
val filteredNodes =
|
||||
if (mapFilterState.onlyFavorites) {
|
||||
allNodes.filter { it.isFavorite || it.num == ourNodeInfo?.num }
|
||||
} else {
|
||||
allNodes
|
||||
}
|
||||
allNodes
|
||||
.filter { node -> !mapFilterState.onlyFavorites || node.isFavorite || node.num == ourNodeInfo?.num }
|
||||
.filter { node ->
|
||||
mapFilterState.lastHeardFilter.seconds == 0L ||
|
||||
(System.currentTimeMillis() / 1000 - node.lastHeard) <= mapFilterState.lastHeardFilter.seconds ||
|
||||
node.num == ourNodeInfo?.num
|
||||
}
|
||||
|
||||
val nodeClusterItems =
|
||||
filteredNodes.map { node ->
|
||||
|
|
|
@ -17,21 +17,33 @@
|
|||
|
||||
package com.geeksville.mesh.ui.map.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.Place
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ui.map.LastHeardFilter
|
||||
import com.geeksville.mesh.ui.map.MapViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
internal fun MapFilterDropdown(expanded: Boolean, onDismissRequest: () -> Unit, mapViewModel: MapViewModel) {
|
||||
|
@ -41,10 +53,7 @@ internal fun MapFilterDropdown(expanded: Boolean, onDismissRequest: () -> Unit,
|
|||
text = { Text(stringResource(id = R.string.only_favorites)) },
|
||||
onClick = { mapViewModel.toggleOnlyFavorites() },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Favorite,
|
||||
contentDescription = stringResource(id = R.string.only_favorites),
|
||||
)
|
||||
Icon(imageVector = Icons.Filled.Star, contentDescription = stringResource(id = R.string.only_favorites))
|
||||
},
|
||||
trailingIcon = {
|
||||
Checkbox(
|
||||
|
@ -85,5 +94,30 @@ internal fun MapFilterDropdown(expanded: Boolean, onDismissRequest: () -> Unit,
|
|||
)
|
||||
},
|
||||
)
|
||||
HorizontalDivider()
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
val filterOptions = LastHeardFilter.entries
|
||||
val selectedIndex = filterOptions.indexOf(mapFilterState.lastHeardFilter)
|
||||
var sliderPosition by remember(selectedIndex) { mutableFloatStateOf(selectedIndex.toFloat()) }
|
||||
|
||||
Text(
|
||||
text =
|
||||
stringResource(
|
||||
R.string.last_heard_filter_label,
|
||||
stringResource(mapFilterState.lastHeardFilter.label),
|
||||
),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
Slider(
|
||||
value = sliderPosition,
|
||||
onValueChange = { sliderPosition = it },
|
||||
onValueChangeFinished = {
|
||||
val newIndex = sliderPosition.roundToInt().coerceIn(0, filterOptions.size - 1)
|
||||
mapViewModel.setLastHeardFilter(filterOptions[newIndex])
|
||||
},
|
||||
valueRange = 0f..(filterOptions.size - 1).toFloat(),
|
||||
steps = filterOptions.size - 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package com.geeksville.mesh.ui.map
|
||||
|
||||
import android.os.RemoteException
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
|
@ -37,7 +38,28 @@ import org.meshtastic.core.database.entity.Packet
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.strings.R
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
sealed class LastHeardFilter(val seconds: Long, @StringRes val label: Int) {
|
||||
data object Any : LastHeardFilter(0L, R.string.any)
|
||||
|
||||
data object OneHour : LastHeardFilter(TimeUnit.HOURS.toSeconds(1), R.string.one_hour)
|
||||
|
||||
data object EightHours : LastHeardFilter(TimeUnit.HOURS.toSeconds(8), R.string.eight_hours)
|
||||
|
||||
data object OneDay : LastHeardFilter(TimeUnit.DAYS.toSeconds(1), R.string.one_day)
|
||||
|
||||
data object TwoDays : LastHeardFilter(TimeUnit.DAYS.toSeconds(2), R.string.two_days)
|
||||
|
||||
companion object {
|
||||
fun fromSeconds(seconds: Long): LastHeardFilter = entries.find { it.seconds == seconds } ?: Any
|
||||
|
||||
val entries = listOf(Any, OneHour, EightHours, OneDay, TwoDays)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
abstract class BaseMapViewModel(
|
||||
|
@ -80,6 +102,13 @@ abstract class BaseMapViewModel(
|
|||
|
||||
private val showPrecisionCircleOnMap = MutableStateFlow(mapPrefs.showPrecisionCircleOnMap)
|
||||
|
||||
private val lastHeardFilter = MutableStateFlow(LastHeardFilter.fromSeconds(mapPrefs.lastHeardFilter))
|
||||
|
||||
fun setLastHeardFilter(filter: LastHeardFilter) {
|
||||
mapPrefs.lastHeardFilter = filter.seconds
|
||||
lastHeardFilter.value = filter
|
||||
}
|
||||
|
||||
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
|
||||
|
||||
val isConnected =
|
||||
|
@ -133,20 +162,31 @@ abstract class BaseMapViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
data class MapFilterState(val onlyFavorites: Boolean, val showWaypoints: Boolean, val showPrecisionCircle: Boolean)
|
||||
data class MapFilterState(
|
||||
val onlyFavorites: Boolean,
|
||||
val showWaypoints: Boolean,
|
||||
val showPrecisionCircle: Boolean,
|
||||
val lastHeardFilter: LastHeardFilter,
|
||||
)
|
||||
|
||||
val mapFilterStateFlow: StateFlow<MapFilterState> =
|
||||
combine(showOnlyFavorites, showWaypointsOnMap, showPrecisionCircleOnMap) {
|
||||
combine(showOnlyFavorites, showWaypointsOnMap, showPrecisionCircleOnMap, lastHeardFilter) {
|
||||
favoritesOnly,
|
||||
showWaypoints,
|
||||
showPrecisionCircle,
|
||||
lastHeard,
|
||||
->
|
||||
MapFilterState(favoritesOnly, showWaypoints, showPrecisionCircle)
|
||||
MapFilterState(favoritesOnly, showWaypoints, showPrecisionCircle, lastHeard)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue =
|
||||
MapFilterState(showOnlyFavorites.value, showWaypointsOnMap.value, showPrecisionCircleOnMap.value),
|
||||
MapFilterState(
|
||||
showOnlyFavorites.value,
|
||||
showWaypointsOnMap.value,
|
||||
showPrecisionCircleOnMap.value,
|
||||
lastHeardFilter.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ interface MapPrefs {
|
|||
var showOnlyFavorites: Boolean
|
||||
var showWaypointsOnMap: Boolean
|
||||
var showPrecisionCircleOnMap: Boolean
|
||||
var lastHeardFilter: Long
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
@ -37,4 +38,5 @@ class MapPrefsImpl @Inject constructor(@MapSharedPreferences prefs: SharedPrefer
|
|||
override var showOnlyFavorites: Boolean by PrefDelegate(prefs, "show_only_favorites", false)
|
||||
override var showWaypointsOnMap: Boolean by PrefDelegate(prefs, "show_waypoints", true)
|
||||
override var showPrecisionCircleOnMap: Boolean by PrefDelegate(prefs, "show_precision_circle", true)
|
||||
override var lastHeardFilter: Long by PrefDelegate(prefs, "last_heard_filter", 0L)
|
||||
}
|
||||
|
|
|
@ -901,4 +901,10 @@
|
|||
<string name="remotely_administrating">"[Remote] %1$s"</string>
|
||||
<string name="device_telemetry_enabled">Send Device Telemetry</string>
|
||||
<string name="device_telemetry_enabled_summary">Enable/Disable the device telemetry module to send metrics to the mesh</string>
|
||||
<string name="any">Any</string>
|
||||
<string name="one_hour">1 Hour</string>
|
||||
<string name="eight_hours">8 Hours</string>
|
||||
<string name="one_day">24 Hours</string>
|
||||
<string name="two_days">48 Hours</string>
|
||||
<string name="last_heard_filter_label">Filter by Last Heard time: %s</string>
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue