kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
refactor: decouple `NavGraph` from ViewModel and NodeEntity
rodzic
6678df78b0
commit
716a3f535f
|
@ -35,17 +35,20 @@ import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.database.MeshLogRepository
|
import com.geeksville.mesh.database.MeshLogRepository
|
||||||
import com.geeksville.mesh.database.entity.MeshLog
|
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.model.map.CustomTileSource
|
||||||
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 com.geeksville.mesh.ui.map.MAP_STYLE_ID
|
import com.geeksville.mesh.ui.map.MAP_STYLE_ID
|
||||||
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.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
@ -63,6 +66,7 @@ data class MetricsState(
|
||||||
val isManaged: Boolean = true,
|
val isManaged: Boolean = true,
|
||||||
val isFahrenheit: Boolean = false,
|
val isFahrenheit: Boolean = false,
|
||||||
val displayUnits: DisplayUnits = DisplayUnits.METRIC,
|
val displayUnits: DisplayUnits = DisplayUnits.METRIC,
|
||||||
|
val node: NodeEntity? = null,
|
||||||
val deviceMetrics: List<Telemetry> = emptyList(),
|
val deviceMetrics: List<Telemetry> = emptyList(),
|
||||||
val environmentMetrics: List<Telemetry> = emptyList(),
|
val environmentMetrics: List<Telemetry> = emptyList(),
|
||||||
val signalMetrics: List<MeshPacket> = emptyList(),
|
val signalMetrics: List<MeshPacket> = emptyList(),
|
||||||
|
@ -160,6 +164,13 @@ class MetricsViewModel @Inject constructor(
|
||||||
val timeFrame: StateFlow<TimeFrame> = _timeFrame
|
val timeFrame: StateFlow<TimeFrame> = _timeFrame
|
||||||
|
|
||||||
init {
|
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 ->
|
radioConfigRepository.deviceProfileFlow.onEach { profile ->
|
||||||
val moduleConfig = profile.moduleConfig
|
val moduleConfig = profile.moduleConfig
|
||||||
_state.update { state ->
|
_state.update { state ->
|
||||||
|
|
|
@ -68,6 +68,7 @@ import javax.inject.Inject
|
||||||
* Data class that represents the current RadioConfig state.
|
* Data class that represents the current RadioConfig state.
|
||||||
*/
|
*/
|
||||||
data class RadioConfigState(
|
data class RadioConfigState(
|
||||||
|
val isLocal: Boolean = false,
|
||||||
val connected: Boolean = false,
|
val connected: Boolean = false,
|
||||||
val route: String = "",
|
val route: String = "",
|
||||||
val metadata: MeshProtos.DeviceMetadata = MeshProtos.DeviceMetadata.getDefaultInstance(),
|
val metadata: MeshProtos.DeviceMetadata = MeshProtos.DeviceMetadata.getDefaultInstance(),
|
||||||
|
@ -121,6 +122,10 @@ class RadioConfigViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
radioConfigRepository.myNodeInfo.onEach { ni ->
|
||||||
|
_radioConfigState.update { it.copy(isLocal = destNum == null || destNum == ni?.myNodeNum) }
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
debug("RadioConfigViewModel created")
|
debug("RadioConfigViewModel created")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,6 @@ import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.android.Logging
|
import com.geeksville.mesh.android.Logging
|
||||||
import com.geeksville.mesh.database.entity.NodeEntity
|
|
||||||
import com.geeksville.mesh.model.MetricsViewModel
|
import com.geeksville.mesh.model.MetricsViewModel
|
||||||
import com.geeksville.mesh.model.RadioConfigViewModel
|
import com.geeksville.mesh.model.RadioConfigViewModel
|
||||||
import com.geeksville.mesh.ui.components.DeviceMetricsScreen
|
import com.geeksville.mesh.ui.components.DeviceMetricsScreen
|
||||||
|
@ -163,8 +162,6 @@ class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
NavGraph(
|
NavGraph(
|
||||||
node = node,
|
|
||||||
viewModel = model,
|
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = startDestination,
|
startDestination = startDestination,
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
|
@ -290,8 +287,6 @@ private fun MeshAppBar(
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun NavGraph(
|
fun NavGraph(
|
||||||
node: NodeEntity?,
|
|
||||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
|
||||||
navController: NavHostController = rememberNavController(),
|
navController: NavHostController = rememberNavController(),
|
||||||
startDestination: Any,
|
startDestination: Any,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -302,9 +297,7 @@ fun NavGraph(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
composable<Route.NodeDetail> {
|
composable<Route.NodeDetail> {
|
||||||
NodeDetailScreen(
|
NodeDetailScreen { navController.navigate(route = it) }
|
||||||
node = node,
|
|
||||||
) { navController.navigate(route = it) }
|
|
||||||
}
|
}
|
||||||
composable<Route.DeviceMetrics> {
|
composable<Route.DeviceMetrics> {
|
||||||
val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
|
val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
|
||||||
|
@ -331,79 +324,99 @@ fun NavGraph(
|
||||||
TracerouteLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))
|
TracerouteLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.RadioConfig> {
|
composable<Route.RadioConfig> {
|
||||||
RadioConfigScreen(
|
RadioConfigScreen { navController.navigate(route = it) }
|
||||||
node = node,
|
|
||||||
viewModel = viewModel,
|
|
||||||
) { navController.navigate(route = it) }
|
|
||||||
}
|
}
|
||||||
composable<Route.User> {
|
composable<Route.User> {
|
||||||
UserConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
UserConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Channels> {
|
composable<Route.Channels> {
|
||||||
ChannelConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
ChannelConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Device> {
|
composable<Route.Device> {
|
||||||
DeviceConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
DeviceConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Position> {
|
composable<Route.Position> {
|
||||||
PositionConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
PositionConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Power> {
|
composable<Route.Power> {
|
||||||
PowerConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
PowerConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Network> {
|
composable<Route.Network> {
|
||||||
NetworkConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
NetworkConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Display> {
|
composable<Route.Display> {
|
||||||
DisplayConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
DisplayConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.LoRa> {
|
composable<Route.LoRa> {
|
||||||
LoRaConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
LoRaConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Bluetooth> {
|
composable<Route.Bluetooth> {
|
||||||
BluetoothConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
BluetoothConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Security> {
|
composable<Route.Security> {
|
||||||
SecurityConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
SecurityConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.MQTT> {
|
composable<Route.MQTT> {
|
||||||
MQTTConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
MQTTConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Serial> {
|
composable<Route.Serial> {
|
||||||
SerialConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
SerialConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.ExtNotification> {
|
composable<Route.ExtNotification> {
|
||||||
ExternalNotificationConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
ExternalNotificationConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.StoreForward> {
|
composable<Route.StoreForward> {
|
||||||
StoreForwardConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
StoreForwardConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.RangeTest> {
|
composable<Route.RangeTest> {
|
||||||
RangeTestConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
RangeTestConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Telemetry> {
|
composable<Route.Telemetry> {
|
||||||
TelemetryConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
TelemetryConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.CannedMessage> {
|
composable<Route.CannedMessage> {
|
||||||
CannedMessageConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
CannedMessageConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Audio> {
|
composable<Route.Audio> {
|
||||||
AudioConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
AudioConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.RemoteHardware> {
|
composable<Route.RemoteHardware> {
|
||||||
RemoteHardwareConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
RemoteHardwareConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.NeighborInfo> {
|
composable<Route.NeighborInfo> {
|
||||||
NeighborInfoConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
NeighborInfoConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.AmbientLighting> {
|
composable<Route.AmbientLighting> {
|
||||||
AmbientLightingConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
AmbientLightingConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.DetectionSensor> {
|
composable<Route.DetectionSensor> {
|
||||||
DetectionSensorConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
DetectionSensorConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
composable<Route.Paxcounter> {
|
composable<Route.Paxcounter> {
|
||||||
PaxcounterConfigScreen(viewModel)
|
val parentEntry = remember { navController.getBackStackEntry<Route.RadioConfig>() }
|
||||||
|
PaxcounterConfigScreen(hiltViewModel<RadioConfigViewModel>(parentEntry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,14 +98,14 @@ import kotlin.math.ln
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NodeDetailScreen(
|
fun NodeDetailScreen(
|
||||||
node: NodeEntity?,
|
|
||||||
viewModel: MetricsViewModel = hiltViewModel(),
|
viewModel: MetricsViewModel = hiltViewModel(),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onNavigate: (Any) -> Unit,
|
onNavigate: (Any) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (node != null) {
|
if (state.node != null) {
|
||||||
|
val node = state.node ?: return
|
||||||
NodeDetailList(
|
NodeDetailList(
|
||||||
node = node,
|
node = node,
|
||||||
metricsState = state,
|
metricsState = state,
|
||||||
|
|
|
@ -34,8 +34,8 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.AlertDialog
|
import androidx.compose.material.AlertDialog
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
|
@ -65,7 +65,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.database.entity.NodeEntity
|
|
||||||
import com.geeksville.mesh.model.RadioConfigViewModel
|
import com.geeksville.mesh.model.RadioConfigViewModel
|
||||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||||
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
|
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
|
||||||
|
@ -79,12 +78,10 @@ private fun getNavRouteFrom(routeName: String): Any? {
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun RadioConfigScreen(
|
fun RadioConfigScreen(
|
||||||
node: NodeEntity?,
|
|
||||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onNavigate: (Any) -> Unit = {}
|
onNavigate: (Any) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val isLocal = node?.num == viewModel.myNodeNum
|
|
||||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||||
var isWaiting by remember { mutableStateOf(false) }
|
var isWaiting by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
@ -140,7 +137,7 @@ fun RadioConfigScreen(
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = "application/*"
|
type = "application/*"
|
||||||
putExtra(Intent.EXTRA_TITLE, "${node!!.num.toUInt()}.cfg")
|
putExtra(Intent.EXTRA_TITLE, "device_profile.cfg")
|
||||||
}
|
}
|
||||||
exportConfigLauncher.launch(intent)
|
exportConfigLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
@ -154,7 +151,7 @@ fun RadioConfigScreen(
|
||||||
|
|
||||||
RadioConfigItemList(
|
RadioConfigItemList(
|
||||||
enabled = state.connected && !isWaiting,
|
enabled = state.connected && !isWaiting,
|
||||||
isLocal = isLocal,
|
isLocal = state.isLocal,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onRouteClick = { route ->
|
onRouteClick = { route ->
|
||||||
isWaiting = true
|
isWaiting = true
|
||||||
|
|
Ładowanie…
Reference in New Issue