From 716a3f535ff457106ceb83d4dbb2c48bb11258db Mon Sep 17 00:00:00 2001 From: andrekir Date: Sat, 30 Nov 2024 07:50:15 -0300 Subject: [PATCH] refactor: decouple `NavGraph` from ViewModel and NodeEntity --- .../geeksville/mesh/model/MetricsViewModel.kt | 11 +++ .../mesh/model/RadioConfigViewModel.kt | 5 ++ .../java/com/geeksville/mesh/ui/NavGraph.kt | 83 +++++++++++-------- .../java/com/geeksville/mesh/ui/NodeDetail.kt | 4 +- .../geeksville/mesh/ui/RadioConfigScreen.kt | 9 +- 5 files changed, 69 insertions(+), 43 deletions(-) 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 c50b43f8c..df5b26980 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -35,17 +35,20 @@ import com.geeksville.mesh.TelemetryProtos.Telemetry import com.geeksville.mesh.android.Logging import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.entity.MeshLog +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.model.map.CustomTileSource import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.ui.Route import com.geeksville.mesh.ui.map.MAP_STYLE_ID import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.update @@ -63,6 +66,7 @@ data class MetricsState( val isManaged: Boolean = true, val isFahrenheit: Boolean = false, val displayUnits: DisplayUnits = DisplayUnits.METRIC, + val node: NodeEntity? = null, val deviceMetrics: List = emptyList(), val environmentMetrics: List = emptyList(), val signalMetrics: List = emptyList(), @@ -160,6 +164,13 @@ class MetricsViewModel @Inject constructor( val timeFrame: StateFlow = _timeFrame init { + @OptIn(ExperimentalCoroutinesApi::class) + radioConfigRepository.nodeDBbyNum + .mapLatest { nodes -> nodes[destNum] } + .distinctUntilChanged() + .onEach { node -> _state.update { state -> state.copy(node = node) } } + .launchIn(viewModelScope) + radioConfigRepository.deviceProfileFlow.onEach { profile -> val moduleConfig = profile.moduleConfig _state.update { state -> diff --git a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt index f43854a08..9ff528aee 100644 --- a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt @@ -68,6 +68,7 @@ import javax.inject.Inject * Data class that represents the current RadioConfig state. */ data class RadioConfigState( + val isLocal: Boolean = false, val connected: Boolean = false, val route: String = "", val metadata: MeshProtos.DeviceMetadata = MeshProtos.DeviceMetadata.getDefaultInstance(), @@ -121,6 +122,10 @@ class RadioConfigViewModel @Inject constructor( } }.launchIn(viewModelScope) + radioConfigRepository.myNodeInfo.onEach { ni -> + _radioConfigState.update { it.copy(isLocal = destNum == null || destNum == ni?.myNodeNum) } + }.launchIn(viewModelScope) + debug("RadioConfigViewModel created") } diff --git a/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt index 03d41c9d6..133a45ab5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt @@ -73,7 +73,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.RadioConfigViewModel import com.geeksville.mesh.ui.components.DeviceMetricsScreen @@ -163,8 +162,6 @@ class NavGraphFragment : ScreenFragment("NavGraph"), Logging { } ) { innerPadding -> NavGraph( - node = node, - viewModel = model, navController = navController, startDestination = startDestination, modifier = Modifier.padding(innerPadding), @@ -290,8 +287,6 @@ private fun MeshAppBar( @Suppress("LongMethod") @Composable fun NavGraph( - node: NodeEntity?, - viewModel: RadioConfigViewModel = hiltViewModel(), navController: NavHostController = rememberNavController(), startDestination: Any, modifier: Modifier = Modifier, @@ -302,9 +297,7 @@ fun NavGraph( modifier = modifier, ) { composable { - NodeDetailScreen( - node = node, - ) { navController.navigate(route = it) } + NodeDetailScreen { navController.navigate(route = it) } } composable { val parentEntry = remember { navController.getBackStackEntry() } @@ -331,79 +324,99 @@ fun NavGraph( TracerouteLogScreen(hiltViewModel(parentEntry)) } composable { - RadioConfigScreen( - node = node, - viewModel = viewModel, - ) { navController.navigate(route = it) } + RadioConfigScreen { navController.navigate(route = it) } } composable { - UserConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + UserConfigScreen(hiltViewModel(parentEntry)) } composable { - ChannelConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + ChannelConfigScreen(hiltViewModel(parentEntry)) } composable { - DeviceConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + DeviceConfigScreen(hiltViewModel(parentEntry)) } composable { - PositionConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + PositionConfigScreen(hiltViewModel(parentEntry)) } composable { - PowerConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + PowerConfigScreen(hiltViewModel(parentEntry)) } composable { - NetworkConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + NetworkConfigScreen(hiltViewModel(parentEntry)) } composable { - DisplayConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + DisplayConfigScreen(hiltViewModel(parentEntry)) } composable { - LoRaConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + LoRaConfigScreen(hiltViewModel(parentEntry)) } composable { - BluetoothConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + BluetoothConfigScreen(hiltViewModel(parentEntry)) } composable { - SecurityConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + SecurityConfigScreen(hiltViewModel(parentEntry)) } composable { - MQTTConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + MQTTConfigScreen(hiltViewModel(parentEntry)) } composable { - SerialConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + SerialConfigScreen(hiltViewModel(parentEntry)) } composable { - ExternalNotificationConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + ExternalNotificationConfigScreen(hiltViewModel(parentEntry)) } composable { - StoreForwardConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + StoreForwardConfigScreen(hiltViewModel(parentEntry)) } composable { - RangeTestConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + RangeTestConfigScreen(hiltViewModel(parentEntry)) } composable { - TelemetryConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + TelemetryConfigScreen(hiltViewModel(parentEntry)) } composable { - CannedMessageConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + CannedMessageConfigScreen(hiltViewModel(parentEntry)) } composable { - AudioConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + AudioConfigScreen(hiltViewModel(parentEntry)) } composable { - RemoteHardwareConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + RemoteHardwareConfigScreen(hiltViewModel(parentEntry)) } composable { - NeighborInfoConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + NeighborInfoConfigScreen(hiltViewModel(parentEntry)) } composable { - AmbientLightingConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + AmbientLightingConfigScreen(hiltViewModel(parentEntry)) } composable { - DetectionSensorConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + DetectionSensorConfigScreen(hiltViewModel(parentEntry)) } composable { - PaxcounterConfigScreen(viewModel) + val parentEntry = remember { navController.getBackStackEntry() } + PaxcounterConfigScreen(hiltViewModel(parentEntry)) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt index 2f61028e5..fd179a690 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt @@ -98,14 +98,14 @@ import kotlin.math.ln @Composable fun NodeDetailScreen( - node: NodeEntity?, viewModel: MetricsViewModel = hiltViewModel(), modifier: Modifier = Modifier, onNavigate: (Any) -> Unit, ) { val state by viewModel.state.collectAsStateWithLifecycle() - if (node != null) { + if (state.node != null) { + val node = state.node ?: return NodeDetailList( node = node, metricsState = state, diff --git a/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt index 17980e1aa..1a1ca7eb7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt @@ -34,8 +34,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.AlertDialog import androidx.compose.material.Button @@ -65,7 +65,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile import com.geeksville.mesh.R -import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.model.RadioConfigViewModel import com.geeksville.mesh.ui.components.PreferenceCategory import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog @@ -79,12 +78,10 @@ private fun getNavRouteFrom(routeName: String): Any? { @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable fun RadioConfigScreen( - node: NodeEntity?, viewModel: RadioConfigViewModel = hiltViewModel(), modifier: Modifier = Modifier, onNavigate: (Any) -> Unit = {} ) { - val isLocal = node?.num == viewModel.myNodeNum val state by viewModel.radioConfigState.collectAsStateWithLifecycle() var isWaiting by remember { mutableStateOf(false) } @@ -140,7 +137,7 @@ fun RadioConfigScreen( val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/*" - putExtra(Intent.EXTRA_TITLE, "${node!!.num.toUInt()}.cfg") + putExtra(Intent.EXTRA_TITLE, "device_profile.cfg") } exportConfigLauncher.launch(intent) } @@ -154,7 +151,7 @@ fun RadioConfigScreen( RadioConfigItemList( enabled = state.connected && !isWaiting, - isLocal = isLocal, + isLocal = state.isLocal, modifier = modifier, onRouteClick = { route -> isWaiting = true