mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: pass nav args to ViewModel using SavedStateHandle
This commit is contained in:
parent
296f1944b7
commit
1ae65ef267
4 changed files with 38 additions and 64 deletions
|
|
@ -2,8 +2,10 @@ package com.geeksville.mesh.model
|
|||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.toRoute
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
|
|
@ -14,12 +16,11 @@ import com.geeksville.mesh.android.Logging
|
|||
import com.geeksville.mesh.database.MeshLogRepository
|
||||
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
|
||||
|
|
@ -65,15 +66,16 @@ private fun MeshPacket.toPosition(): Position? = if (!decoded.wantResponse) {
|
|||
|
||||
@HiltViewModel
|
||||
class MetricsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val app: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val meshLogRepository: MeshLogRepository,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
) : ViewModel(), Logging {
|
||||
private val destNum = MutableStateFlow(0)
|
||||
private val destNum = savedStateHandle.toRoute<Route.NodeDetail>().destNum
|
||||
|
||||
private fun MeshLog.hasValidTraceroute(): Boolean = with(fromRadio.packet) {
|
||||
hasDecoded() && decoded.wantResponse && from == 0 && to == destNum.value
|
||||
hasDecoded() && decoded.wantResponse && from == 0 && to == destNum
|
||||
}
|
||||
|
||||
fun getUser(nodeNum: Int) = radioConfigRepository.getUser(nodeNum)
|
||||
|
|
@ -83,7 +85,7 @@ class MetricsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun clearPosition() = viewModelScope.launch(dispatchers.io) {
|
||||
meshLogRepository.deleteLogs(destNum.value, PortNum.POSITION_APP_VALUE)
|
||||
meshLogRepository.deleteLogs(destNum, PortNum.POSITION_APP_VALUE)
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow(MetricsState.Empty)
|
||||
|
|
@ -100,51 +102,37 @@ class MetricsViewModel @Inject constructor(
|
|||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
destNum.flatMapLatest { destNum ->
|
||||
meshLogRepository.getTelemetryFrom(destNum).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)
|
||||
destNum.flatMapLatest { destNum ->
|
||||
meshLogRepository.getMeshPacketsFrom(destNum).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)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
destNum.flatMapLatest { destNum ->
|
||||
combine(
|
||||
meshLogRepository.getLogsFrom(nodeNum = 0, PortNum.TRACEROUTE_APP_VALUE),
|
||||
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.TRACEROUTE_APP_VALUE),
|
||||
) { request, response ->
|
||||
_state.update { state ->
|
||||
state.copy(
|
||||
tracerouteRequests = request.filter { it.hasValidTraceroute() },
|
||||
tracerouteResults = response,
|
||||
)
|
||||
}
|
||||
combine(
|
||||
meshLogRepository.getLogsFrom(nodeNum = 0, PortNum.TRACEROUTE_APP_VALUE),
|
||||
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.TRACEROUTE_APP_VALUE),
|
||||
) { request, response ->
|
||||
_state.update { state ->
|
||||
state.copy(
|
||||
tracerouteRequests = request.filter { it.hasValidTraceroute() },
|
||||
tracerouteResults = response,
|
||||
)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
destNum.flatMapLatest { destNum ->
|
||||
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.POSITION_APP_VALUE).onEach { packets ->
|
||||
_state.update { state ->
|
||||
state.copy(positionLogs = packets.mapNotNull { it.toPosition() })
|
||||
}
|
||||
}
|
||||
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.POSITION_APP_VALUE).onEach { packets ->
|
||||
_state.update { state -> state.copy(positionLogs = packets.mapNotNull { it.toPosition() }) }
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
debug("MetricsViewModel created")
|
||||
|
|
@ -155,13 +143,6 @@ class MetricsViewModel @Inject constructor(
|
|||
debug("MetricsViewModel cleared")
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to set the Node for which the user will see charts for.
|
||||
*/
|
||||
fun setSelectedNode(nodeNum: Int) {
|
||||
destNum.value = nodeNum
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the persisted Position data out to a CSV file in the specified location.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ package com.geeksville.mesh.model
|
|||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.toRoute
|
||||
import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
|
|
@ -27,14 +29,17 @@ import com.geeksville.mesh.ui.AdminRoute
|
|||
import com.geeksville.mesh.ui.ConfigRoute
|
||||
import com.geeksville.mesh.ui.ModuleRoute
|
||||
import com.geeksville.mesh.ui.ResponseState
|
||||
import com.geeksville.mesh.ui.Route
|
||||
import com.google.protobuf.MessageLite
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -62,24 +67,16 @@ data class RadioConfigState(
|
|||
|
||||
@HiltViewModel
|
||||
class RadioConfigViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val app: Application,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
) : ViewModel(), Logging {
|
||||
|
||||
private val meshService: IMeshService? get() = radioConfigRepository.meshService
|
||||
|
||||
private val _destNum = MutableStateFlow<Int?>(null)
|
||||
private val destNum = savedStateHandle.toRoute<Route.RadioConfig>().destNum
|
||||
private val _destNode = MutableStateFlow<NodeEntity?>(null)
|
||||
val destNode: StateFlow<NodeEntity?> get() = _destNode
|
||||
|
||||
/**
|
||||
* Sets the destination [NodeEntity] used in Radio Configuration.
|
||||
* @param num Destination nodeNum (or null for our local [NodeEntity]).
|
||||
*/
|
||||
fun setDestNum(num: Int?) {
|
||||
_destNum.value = num
|
||||
}
|
||||
|
||||
private val requestIds = MutableStateFlow(hashSetOf<Int>())
|
||||
private val _radioConfigState = MutableStateFlow(RadioConfigState())
|
||||
val radioConfigState: StateFlow<RadioConfigState> = _radioConfigState
|
||||
|
|
@ -88,7 +85,8 @@ class RadioConfigViewModel @Inject constructor(
|
|||
val currentDeviceProfile get() = _currentDeviceProfile.value
|
||||
|
||||
init {
|
||||
combine(_destNum, radioConfigRepository.nodeDBbyNum) { destNum, nodes ->
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
radioConfigRepository.nodeDBbyNum.mapLatest { nodes ->
|
||||
nodes[destNum] ?: nodes.values.firstOrNull()
|
||||
}.onEach { _destNode.value = it }.launchIn(viewModelScope)
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
|
|||
"NodeDetails" -> Route.NodeDetail(destNum!!)
|
||||
else -> Route.RadioConfig(destNum)
|
||||
}
|
||||
model.setDestNum(destNum)
|
||||
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import androidx.compose.material.icons.filled.Thermostat
|
|||
import androidx.compose.material.icons.filled.WaterDrop
|
||||
import androidx.compose.material.icons.filled.Work
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -82,9 +81,6 @@ fun NodeDetailScreen(
|
|||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
if (node != null) {
|
||||
LaunchedEffect(node.num) {
|
||||
viewModel.setSelectedNode(node.num)
|
||||
}
|
||||
NodeDetailList(
|
||||
node = node,
|
||||
metricsState = state,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue