refactor: Conducting time filter at the component lvl to avoid metric nav cards from being disabled when we don't have recent data (#1402)

pull/1403/head
Robert-0410 2024-11-13 02:24:40 -08:00 zatwierdzone przez GitHub
rodzic f6af9b8782
commit 013e3de792
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
7 zmienionych plików z 54 dodań i 50 usunięć

Wyświetl plik

@ -32,12 +32,9 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
.toBuilder().setTime((log.received_date / MILLIS_TO_SECONDS).toInt()).build() .toBuilder().setTime((log.received_date / MILLIS_TO_SECONDS).toInt()).build()
}.getOrNull() }.getOrNull()
/**
* @param timeFrame the oldest [Telemetry] to get.
*/
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun getTelemetryFrom(nodeNum: Int, timeFrame: Long): Flow<List<Telemetry>> = fun getTelemetryFrom(nodeNum: Int): Flow<List<Telemetry>> =
meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS, timeFrame) meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS)
.distinctUntilChanged() .distinctUntilChanged()
.mapLatest { list -> list.mapNotNull(::parseTelemetryLog) } .mapLatest { list -> list.mapNotNull(::parseTelemetryLog) }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
@ -46,8 +43,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
nodeNum: Int, nodeNum: Int,
portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE, portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE,
maxItem: Int = MAX_MESH_PACKETS, maxItem: Int = MAX_MESH_PACKETS,
oldestTime: Long = 0L ): Flow<List<MeshLog>> = meshLogDao.getLogsFrom(nodeNum, portNum, maxItem)
): Flow<List<MeshLog>> = meshLogDao.getLogsFrom(nodeNum, portNum, maxItem, oldestTime)
.distinctUntilChanged() .distinctUntilChanged()
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
@ -59,8 +55,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
fun getMeshPacketsFrom( fun getMeshPacketsFrom(
nodeNum: Int, nodeNum: Int,
portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE, portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE,
oldestTime: Long = 0L ): Flow<List<MeshPacket>> = getLogsFrom(nodeNum, portNum)
): Flow<List<MeshPacket>> = getLogsFrom(nodeNum, portNum, oldestTime = oldestTime)
.mapLatest { list -> list.map { it.fromRadio.packet } } .mapLatest { list -> list.map { it.fromRadio.packet } }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)

Wyświetl plik

@ -19,16 +19,15 @@ interface MeshLogDao {
* Retrieves [MeshLog]s matching 'from_num' (nodeNum) and 'port_num' (PortNum). * Retrieves [MeshLog]s matching 'from_num' (nodeNum) and 'port_num' (PortNum).
* *
* @param portNum If 0, returns all MeshPackets. Otherwise, filters by 'port_num'. * @param portNum If 0, returns all MeshPackets. Otherwise, filters by 'port_num'.
* @param timeFrame oldest limit in milliseconds of [MeshLog]s we want to retrieve.
*/ */
@Query( @Query(
""" """
SELECT * FROM log SELECT * FROM log
WHERE from_num = :fromNum AND (:portNum = 0 AND port_num != 0 OR port_num = :portNum) AND received_date > :timeFrame WHERE from_num = :fromNum AND (:portNum = 0 AND port_num != 0 OR port_num = :portNum)
ORDER BY received_date DESC LIMIT 0,:maxItem ORDER BY received_date DESC LIMIT 0,:maxItem
""" """
) )
fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int, timeFrame: Long = 0L): Flow<List<MeshLog>> fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int): Flow<List<MeshLog>>
@Insert @Insert
fun insert(log: MeshLog) fun insert(log: MeshLog)

Wyświetl plik

@ -20,11 +20,9 @@ import com.geeksville.mesh.database.entity.MeshLog
import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.ui.Route import com.geeksville.mesh.ui.Route
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -35,6 +33,7 @@ import java.io.FileNotFoundException
import java.io.FileWriter import java.io.FileWriter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
data class MetricsState( data class MetricsState(
@ -54,6 +53,21 @@ data class MetricsState(
fun hasTracerouteLogs() = tracerouteRequests.isNotEmpty() fun hasTracerouteLogs() = tracerouteRequests.isNotEmpty()
fun hasPositionLogs() = positionLogs.isNotEmpty() fun hasPositionLogs() = positionLogs.isNotEmpty()
fun deviceMetricsFiltered(timeFrame: TimeFrame): List<Telemetry> {
val oldestTime = timeFrame.calculateOldestTime()
return deviceMetrics.filter { it.time >= oldestTime }
}
fun environmentMetricsFiltered(timeFrame: TimeFrame): List<Telemetry> {
val oldestTime = timeFrame.calculateOldestTime()
return environmentMetrics.filter { it.time >= oldestTime }
}
fun signalMetricsFiltered(timeFrame: TimeFrame): List<MeshPacket> {
val oldestTime = timeFrame.calculateOldestTime()
return signalMetrics.filter { it.rxTime >= oldestTime }
}
companion object { companion object {
val Empty = MetricsState() val Empty = MetricsState()
} }
@ -64,20 +78,20 @@ data class MetricsState(
*/ */
@Suppress("MagicNumber") @Suppress("MagicNumber")
enum class TimeFrame( enum class TimeFrame(
val milliseconds: Long, val seconds: Long,
@StringRes val strRes: Int @StringRes val strRes: Int
) { ) {
TWENTY_FOUR_HOURS(86400000L, R.string.twenty_four_hours), TWENTY_FOUR_HOURS(TimeUnit.DAYS.toSeconds(1), R.string.twenty_four_hours),
FORTY_EIGHT_HOURS(172800000L, R.string.forty_eight_hours), FORTY_EIGHT_HOURS(TimeUnit.DAYS.toSeconds(2), R.string.forty_eight_hours),
ONE_WEEK(604800000L, R.string.one_week), ONE_WEEK(TimeUnit.DAYS.toSeconds(7), R.string.one_week),
TWO_WEEKS(1209600000L, R.string.two_weeks), TWO_WEEKS(TimeUnit.DAYS.toSeconds(14), R.string.two_weeks),
ONE_MONTH(2629800000L, R.string.one_month), FOUR_WEEKS(TimeUnit.DAYS.toSeconds(28), R.string.four_weeks),
MAX(0L, R.string.max); MAX(0L, R.string.max);
fun calculateOldestTime(): Long = if (this == MAX) { fun calculateOldestTime(): Long = if (this == MAX) {
MAX.milliseconds MAX.seconds
} else { } else {
System.currentTimeMillis() - this.milliseconds System.currentTimeMillis() / 1000 - this.seconds
} }
} }
@ -131,27 +145,20 @@ class MetricsViewModel @Inject constructor(
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
@OptIn(ExperimentalCoroutinesApi::class) meshLogRepository.getTelemetryFrom(destNum).onEach { telemetry ->
_timeFrame.flatMapLatest { timeFrame -> _state.update { state ->
meshLogRepository.getTelemetryFrom(destNum, timeFrame.calculateOldestTime()).onEach { telemetry -> state.copy(
_state.update { state -> deviceMetrics = telemetry.filter { it.hasDeviceMetrics() },
state.copy( environmentMetrics = telemetry.filter {
deviceMetrics = telemetry.filter { it.hasDeviceMetrics() }, it.hasEnvironmentMetrics() && it.environmentMetrics.relativeHumidity >= 0f
environmentMetrics = telemetry.filter { }
it.hasEnvironmentMetrics() && it.environmentMetrics.relativeHumidity >= 0f )
},
)
}
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
@OptIn(ExperimentalCoroutinesApi::class) meshLogRepository.getMeshPacketsFrom(destNum).onEach { meshPackets ->
_timeFrame.flatMapLatest { timeFrame -> _state.update { state ->
val oldestTime = timeFrame.calculateOldestTime() state.copy(signalMetrics = meshPackets.filter { it.hasValidSignal() })
meshLogRepository.getMeshPacketsFrom(destNum, oldestTime = oldestTime).onEach { meshPackets ->
_state.update { state ->
state.copy(signalMetrics = meshPackets.filter { it.hasValidSignal() })
}
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)

Wyświetl plik

@ -65,6 +65,8 @@ fun DeviceMetricsScreen(
) { ) {
val state by viewModel.state.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
var displayInfoDialog by remember { mutableStateOf(false) } var displayInfoDialog by remember { mutableStateOf(false) }
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
val data = state.deviceMetricsFiltered(selectedTimeFrame)
Column { Column {
@ -82,11 +84,10 @@ fun DeviceMetricsScreen(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(fraction = 0.33f), .fillMaxHeight(fraction = 0.33f),
state.deviceMetrics.reversed(), data.reversed(),
promptInfoDialog = { displayInfoDialog = true } promptInfoDialog = { displayInfoDialog = true }
) )
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
MetricsTimeSelector( MetricsTimeSelector(
selectedTimeFrame, selectedTimeFrame,
onOptionSelected = { viewModel.setTimeFrame(it) } onOptionSelected = { viewModel.setTimeFrame(it) }
@ -98,7 +99,7 @@ fun DeviceMetricsScreen(
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
items(state.deviceMetrics) { telemetry -> DeviceMetricsCard(telemetry) } items(data) { telemetry -> DeviceMetricsCard(telemetry) }
} }
} }
} }

Wyświetl plik

@ -77,6 +77,8 @@ fun EnvironmentMetricsScreen(
viewModel: MetricsViewModel = hiltViewModel(), viewModel: MetricsViewModel = hiltViewModel(),
) { ) {
val state by viewModel.state.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
val data = state.environmentMetricsFiltered(selectedTimeFrame)
/* Convert Celsius to Fahrenheit */ /* Convert Celsius to Fahrenheit */
@Suppress("MagicNumber") @Suppress("MagicNumber")
@ -85,7 +87,7 @@ fun EnvironmentMetricsScreen(
} }
val processedTelemetries: List<Telemetry> = if (state.isFahrenheit) { val processedTelemetries: List<Telemetry> = if (state.isFahrenheit) {
state.environmentMetrics.map { telemetry -> data.map { telemetry ->
val temperatureFahrenheit = val temperatureFahrenheit =
celsiusToFahrenheit(telemetry.environmentMetrics.temperature) celsiusToFahrenheit(telemetry.environmentMetrics.temperature)
telemetry.copy { telemetry.copy {
@ -94,7 +96,7 @@ fun EnvironmentMetricsScreen(
} }
} }
} else { } else {
state.environmentMetrics data
} }
var displayInfoDialog by remember { mutableStateOf(false) } var displayInfoDialog by remember { mutableStateOf(false) }
@ -118,7 +120,6 @@ fun EnvironmentMetricsScreen(
promptInfoDialog = { displayInfoDialog = true } promptInfoDialog = { displayInfoDialog = true }
) )
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
MetricsTimeSelector( MetricsTimeSelector(
selectedTimeFrame, selectedTimeFrame,
onOptionSelected = { viewModel.setTimeFrame(it) } onOptionSelected = { viewModel.setTimeFrame(it) }

Wyświetl plik

@ -71,6 +71,8 @@ fun SignalMetricsScreen(
) { ) {
val state by viewModel.state.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
var displayInfoDialog by remember { mutableStateOf(false) } var displayInfoDialog by remember { mutableStateOf(false) }
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
val data = state.signalMetricsFiltered(selectedTimeFrame)
Column { Column {
@ -88,11 +90,10 @@ fun SignalMetricsScreen(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(fraction = 0.33f), .fillMaxHeight(fraction = 0.33f),
meshPackets = state.signalMetrics.reversed(), meshPackets = data.reversed(),
promptInfoDialog = { displayInfoDialog = true } promptInfoDialog = { displayInfoDialog = true }
) )
val selectedTimeFrame by viewModel.timeFrame.collectAsState()
MetricsTimeSelector( MetricsTimeSelector(
selectedTimeFrame, selectedTimeFrame,
onOptionSelected = { viewModel.setTimeFrame(it) } onOptionSelected = { viewModel.setTimeFrame(it) }
@ -103,7 +104,7 @@ fun SignalMetricsScreen(
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
items(state.signalMetrics) { meshPacket -> SignalMetricsCard(meshPacket) } items(data) { meshPacket -> SignalMetricsCard(meshPacket) }
} }
} }
} }

Wyświetl plik

@ -307,7 +307,7 @@
<string name="forty_eight_hours">48H</string> <string name="forty_eight_hours">48H</string>
<string name="one_week">1W</string> <string name="one_week">1W</string>
<string name="two_weeks">2W</string> <string name="two_weeks">2W</string>
<string name="one_month">1M</string> <string name="four_weeks">4W</string>
<string name="max">Max</string> <string name="max">Max</string>
<string name="selected">Selected</string> <string name="selected">Selected</string>
<string name="not_selected">Not Selected</string> <string name="not_selected">Not Selected</string>