From 013e3de792e078d91c2b4a64fbddc79b8a6684ce Mon Sep 17 00:00:00 2001 From: Robert-0410 <62630290+Robert-0410@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:24:40 -0800 Subject: [PATCH] refactor: Conducting time filter at the component lvl to avoid metric nav cards from being disabled when we don't have recent data (#1402) --- .../mesh/database/MeshLogRepository.kt | 13 ++-- .../mesh/database/dao/MeshLogDao.kt | 5 +- .../geeksville/mesh/model/MetricsViewModel.kt | 63 ++++++++++--------- .../mesh/ui/components/DeviceMetrics.kt | 7 ++- .../mesh/ui/components/EnvironmentMetrics.kt | 7 ++- .../mesh/ui/components/SignalMetrics.kt | 7 ++- app/src/main/res/values/strings.xml | 2 +- 7 files changed, 54 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt b/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt index fcbfebc4a..82c77eeea 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshLogRepository.kt @@ -32,12 +32,9 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L .toBuilder().setTime((log.received_date / MILLIS_TO_SECONDS).toInt()).build() }.getOrNull() - /** - * @param timeFrame the oldest [Telemetry] to get. - */ @OptIn(ExperimentalCoroutinesApi::class) - fun getTelemetryFrom(nodeNum: Int, timeFrame: Long): Flow> = - meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS, timeFrame) + fun getTelemetryFrom(nodeNum: Int): Flow> = + meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS) .distinctUntilChanged() .mapLatest { list -> list.mapNotNull(::parseTelemetryLog) } .flowOn(Dispatchers.IO) @@ -46,8 +43,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L nodeNum: Int, portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE, maxItem: Int = MAX_MESH_PACKETS, - oldestTime: Long = 0L - ): Flow> = meshLogDao.getLogsFrom(nodeNum, portNum, maxItem, oldestTime) + ): Flow> = meshLogDao.getLogsFrom(nodeNum, portNum, maxItem) .distinctUntilChanged() .flowOn(Dispatchers.IO) @@ -59,8 +55,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L fun getMeshPacketsFrom( nodeNum: Int, portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE, - oldestTime: Long = 0L - ): Flow> = getLogsFrom(nodeNum, portNum, oldestTime = oldestTime) + ): Flow> = getLogsFrom(nodeNum, portNum) .mapLatest { list -> list.map { it.fromRadio.packet } } .flowOn(Dispatchers.IO) diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt index 1fdb45389..ae917b9fb 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/MeshLogDao.kt @@ -19,16 +19,15 @@ interface MeshLogDao { * Retrieves [MeshLog]s matching 'from_num' (nodeNum) and 'port_num' (PortNum). * * @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( """ 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 """ ) - fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int, timeFrame: Long = 0L): Flow> + fun getLogsFrom(fromNum: Int, portNum: Int, maxItem: Int): Flow> @Insert fun insert(log: MeshLog) diff --git a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt index 4c031ce14..2afb93728 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -20,11 +20,9 @@ import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.ui.Route import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update @@ -35,6 +33,7 @@ import java.io.FileNotFoundException import java.io.FileWriter import java.text.SimpleDateFormat import java.util.Locale +import java.util.concurrent.TimeUnit import javax.inject.Inject data class MetricsState( @@ -54,6 +53,21 @@ data class MetricsState( fun hasTracerouteLogs() = tracerouteRequests.isNotEmpty() fun hasPositionLogs() = positionLogs.isNotEmpty() + fun deviceMetricsFiltered(timeFrame: TimeFrame): List { + val oldestTime = timeFrame.calculateOldestTime() + return deviceMetrics.filter { it.time >= oldestTime } + } + + fun environmentMetricsFiltered(timeFrame: TimeFrame): List { + val oldestTime = timeFrame.calculateOldestTime() + return environmentMetrics.filter { it.time >= oldestTime } + } + + fun signalMetricsFiltered(timeFrame: TimeFrame): List { + val oldestTime = timeFrame.calculateOldestTime() + return signalMetrics.filter { it.rxTime >= oldestTime } + } + companion object { val Empty = MetricsState() } @@ -64,20 +78,20 @@ data class MetricsState( */ @Suppress("MagicNumber") enum class TimeFrame( - val milliseconds: Long, + val seconds: Long, @StringRes val strRes: Int ) { - TWENTY_FOUR_HOURS(86400000L, R.string.twenty_four_hours), - FORTY_EIGHT_HOURS(172800000L, R.string.forty_eight_hours), - ONE_WEEK(604800000L, R.string.one_week), - TWO_WEEKS(1209600000L, R.string.two_weeks), - ONE_MONTH(2629800000L, R.string.one_month), + TWENTY_FOUR_HOURS(TimeUnit.DAYS.toSeconds(1), R.string.twenty_four_hours), + FORTY_EIGHT_HOURS(TimeUnit.DAYS.toSeconds(2), R.string.forty_eight_hours), + ONE_WEEK(TimeUnit.DAYS.toSeconds(7), R.string.one_week), + TWO_WEEKS(TimeUnit.DAYS.toSeconds(14), R.string.two_weeks), + FOUR_WEEKS(TimeUnit.DAYS.toSeconds(28), R.string.four_weeks), MAX(0L, R.string.max); fun calculateOldestTime(): Long = if (this == MAX) { - MAX.milliseconds + MAX.seconds } else { - System.currentTimeMillis() - this.milliseconds + System.currentTimeMillis() / 1000 - this.seconds } } @@ -131,27 +145,20 @@ class MetricsViewModel @Inject constructor( } }.launchIn(viewModelScope) - @OptIn(ExperimentalCoroutinesApi::class) - _timeFrame.flatMapLatest { timeFrame -> - meshLogRepository.getTelemetryFrom(destNum, timeFrame.calculateOldestTime()).onEach { telemetry -> - _state.update { state -> - state.copy( - deviceMetrics = telemetry.filter { it.hasDeviceMetrics() }, - environmentMetrics = telemetry.filter { - it.hasEnvironmentMetrics() && it.environmentMetrics.relativeHumidity >= 0f - }, - ) - } + meshLogRepository.getTelemetryFrom(destNum).onEach { telemetry -> + _state.update { state -> + state.copy( + deviceMetrics = telemetry.filter { it.hasDeviceMetrics() }, + environmentMetrics = telemetry.filter { + it.hasEnvironmentMetrics() && it.environmentMetrics.relativeHumidity >= 0f + } + ) } }.launchIn(viewModelScope) - @OptIn(ExperimentalCoroutinesApi::class) - _timeFrame.flatMapLatest { timeFrame -> - val oldestTime = timeFrame.calculateOldestTime() - meshLogRepository.getMeshPacketsFrom(destNum, oldestTime = oldestTime).onEach { meshPackets -> - _state.update { state -> - state.copy(signalMetrics = meshPackets.filter { it.hasValidSignal() }) - } + meshLogRepository.getMeshPacketsFrom(destNum).onEach { meshPackets -> + _state.update { state -> + state.copy(signalMetrics = meshPackets.filter { it.hasValidSignal() }) } }.launchIn(viewModelScope) diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt index 0aec01ccf..404a68215 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/DeviceMetrics.kt @@ -65,6 +65,8 @@ fun DeviceMetricsScreen( ) { val state by viewModel.state.collectAsStateWithLifecycle() var displayInfoDialog by remember { mutableStateOf(false) } + val selectedTimeFrame by viewModel.timeFrame.collectAsState() + val data = state.deviceMetricsFiltered(selectedTimeFrame) Column { @@ -82,11 +84,10 @@ fun DeviceMetricsScreen( modifier = Modifier .fillMaxWidth() .fillMaxHeight(fraction = 0.33f), - state.deviceMetrics.reversed(), + data.reversed(), promptInfoDialog = { displayInfoDialog = true } ) - val selectedTimeFrame by viewModel.timeFrame.collectAsState() MetricsTimeSelector( selectedTimeFrame, onOptionSelected = { viewModel.setTimeFrame(it) } @@ -98,7 +99,7 @@ fun DeviceMetricsScreen( LazyColumn( modifier = Modifier.fillMaxSize() ) { - items(state.deviceMetrics) { telemetry -> DeviceMetricsCard(telemetry) } + items(data) { telemetry -> DeviceMetricsCard(telemetry) } } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt index 8afbbc071..2ffd6a81e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/EnvironmentMetrics.kt @@ -77,6 +77,8 @@ fun EnvironmentMetricsScreen( viewModel: MetricsViewModel = hiltViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() + val selectedTimeFrame by viewModel.timeFrame.collectAsState() + val data = state.environmentMetricsFiltered(selectedTimeFrame) /* Convert Celsius to Fahrenheit */ @Suppress("MagicNumber") @@ -85,7 +87,7 @@ fun EnvironmentMetricsScreen( } val processedTelemetries: List = if (state.isFahrenheit) { - state.environmentMetrics.map { telemetry -> + data.map { telemetry -> val temperatureFahrenheit = celsiusToFahrenheit(telemetry.environmentMetrics.temperature) telemetry.copy { @@ -94,7 +96,7 @@ fun EnvironmentMetricsScreen( } } } else { - state.environmentMetrics + data } var displayInfoDialog by remember { mutableStateOf(false) } @@ -118,7 +120,6 @@ fun EnvironmentMetricsScreen( promptInfoDialog = { displayInfoDialog = true } ) - val selectedTimeFrame by viewModel.timeFrame.collectAsState() MetricsTimeSelector( selectedTimeFrame, onOptionSelected = { viewModel.setTimeFrame(it) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/SignalMetrics.kt b/app/src/main/java/com/geeksville/mesh/ui/components/SignalMetrics.kt index fba27394c..0e2f88408 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/SignalMetrics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/SignalMetrics.kt @@ -71,6 +71,8 @@ fun SignalMetricsScreen( ) { val state by viewModel.state.collectAsStateWithLifecycle() var displayInfoDialog by remember { mutableStateOf(false) } + val selectedTimeFrame by viewModel.timeFrame.collectAsState() + val data = state.signalMetricsFiltered(selectedTimeFrame) Column { @@ -88,11 +90,10 @@ fun SignalMetricsScreen( modifier = Modifier .fillMaxWidth() .fillMaxHeight(fraction = 0.33f), - meshPackets = state.signalMetrics.reversed(), + meshPackets = data.reversed(), promptInfoDialog = { displayInfoDialog = true } ) - val selectedTimeFrame by viewModel.timeFrame.collectAsState() MetricsTimeSelector( selectedTimeFrame, onOptionSelected = { viewModel.setTimeFrame(it) } @@ -103,7 +104,7 @@ fun SignalMetricsScreen( LazyColumn( modifier = Modifier.fillMaxSize() ) { - items(state.signalMetrics) { meshPacket -> SignalMetricsCard(meshPacket) } + items(data) { meshPacket -> SignalMetricsCard(meshPacket) } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1ded9395..db9380fcb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -307,7 +307,7 @@ 48H 1W 2W - 1M + 4W Max Selected Not Selected