Skip to content

Commit

Permalink
refactor: decouple NavGraph from ViewModel and NodeEntity
Browse files Browse the repository at this point in the history
  • Loading branch information
andrekir committed Nov 30, 2024
1 parent 6678df7 commit 716a3f5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 43 deletions.
11 changes: 11 additions & 0 deletions app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Telemetry> = emptyList(),
val environmentMetrics: List<Telemetry> = emptyList(),
val signalMetrics: List<MeshPacket> = emptyList(),
Expand Down Expand Up @@ -160,6 +164,13 @@ class MetricsViewModel @Inject constructor(
val timeFrame: StateFlow<TimeFrame> = _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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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")
}

Expand Down
83 changes: 48 additions & 35 deletions app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -163,8 +162,6 @@ class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
}
) { innerPadding ->
NavGraph(
node = node,
viewModel = model,
navController = navController,
startDestination = startDestination,
modifier = Modifier.padding(innerPadding),
Expand Down Expand Up @@ -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,
Expand All @@ -302,9 +297,7 @@ fun NavGraph(
modifier = modifier,
) {
composable<Route.NodeDetail> {
NodeDetailScreen(
node = node,
) { navController.navigate(route = it) }
NodeDetailScreen { navController.navigate(route = it) }
}
composable<Route.DeviceMetrics> {
val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
Expand All @@ -331,79 +324,99 @@ fun NavGraph(
TracerouteLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))
}
composable<Route.RadioConfig> {
RadioConfigScreen(
node = node,
viewModel = viewModel,
) { navController.navigate(route = it) }
RadioConfigScreen { navController.navigate(route = it) }
}
composable<Route.User> {
UserConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
UserConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Channels> {
ChannelConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
ChannelConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Device> {
DeviceConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
DeviceConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Position> {
PositionConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
PositionConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Power> {
PowerConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
PowerConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Network> {
NetworkConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
NetworkConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Display> {
DisplayConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
DisplayConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.LoRa> {
LoRaConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
LoRaConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Bluetooth> {
BluetoothConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
BluetoothConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Security> {
SecurityConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
SecurityConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.MQTT> {
MQTTConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
MQTTConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Serial> {
SerialConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
SerialConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.ExtNotification> {
ExternalNotificationConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
ExternalNotificationConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.StoreForward> {
StoreForwardConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
StoreForwardConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.RangeTest> {
RangeTestConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
RangeTestConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Telemetry> {
TelemetryConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
TelemetryConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.CannedMessage> {
CannedMessageConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
CannedMessageConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Audio> {
AudioConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
AudioConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.RemoteHardware> {
RemoteHardwareConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
RemoteHardwareConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.NeighborInfo> {
NeighborInfoConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
NeighborInfoConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.AmbientLighting> {
AmbientLightingConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
AmbientLightingConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.DetectionSensor> {
DetectionSensorConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
DetectionSensorConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
composable<Route.Paxcounter> {
PaxcounterConfigScreen(viewModel)
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
PaxcounterConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
}
}
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 3 additions & 6 deletions app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) }

Expand Down Expand Up @@ -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)
}
Expand All @@ -154,7 +151,7 @@ fun RadioConfigScreen(

RadioConfigItemList(
enabled = state.connected && !isWaiting,
isLocal = isLocal,
isLocal = state.isLocal,
modifier = modifier,
onRouteClick = { route ->
isWaiting = true
Expand Down

0 comments on commit 716a3f5

Please sign in to comment.