mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Clean up UI state using molecule
This commit is contained in:
parent
dbc5fd74f6
commit
8152a99f47
4 changed files with 85 additions and 69 deletions
|
|
@ -48,6 +48,7 @@ dependencies {
|
|||
implementation(libs.markdown.renderer.android)
|
||||
implementation(libs.markdown.renderer.m3)
|
||||
implementation(libs.markdown.renderer)
|
||||
implementation(libs.molecule)
|
||||
|
||||
googleImplementation(libs.location.services)
|
||||
googleImplementation(libs.maps.compose)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ fun NodeListScreen(
|
|||
scrollToTopEvents: Flow<ScrollToTopEvent>? = null,
|
||||
activeNodeId: Int? = null,
|
||||
) {
|
||||
val state by viewModel.nodesUiState.collectAsStateWithLifecycle()
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val nodes by viewModel.nodeList.collectAsStateWithLifecycle()
|
||||
val ourNode by viewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
|
|
@ -161,7 +161,7 @@ fun NodeListScreen(
|
|||
.background(MaterialTheme.colorScheme.surfaceDim)
|
||||
.padding(8.dp),
|
||||
filterText = state.filter.filterText,
|
||||
onTextChange = { viewModel.nodeFilterText = it },
|
||||
onTextChange = { viewModel.setFilterText(it) },
|
||||
currentSortOption = state.sort,
|
||||
onSortSelect = viewModel::setSortOption,
|
||||
includeUnknown = state.filter.includeUnknown,
|
||||
|
|
|
|||
|
|
@ -17,16 +17,20 @@
|
|||
|
||||
package org.meshtastic.feature.node.list
|
||||
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.AndroidUiDispatcher
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.launchMolecule
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
|
|
@ -38,6 +42,7 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
|||
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
import org.meshtastic.proto.ConfigProtos
|
||||
import org.meshtastic.proto.deviceProfile
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -63,78 +68,87 @@ constructor(
|
|||
private val _sharedContactRequested: MutableStateFlow<AdminProtos.SharedContact?> = MutableStateFlow(null)
|
||||
val sharedContactRequested = _sharedContactRequested.asStateFlow()
|
||||
|
||||
private val nodeSortOption = nodeFilterPreferences.nodeSortOption
|
||||
private val filterText = savedStateHandle.getStateFlow(KEY_FILTER_TEXT, "")
|
||||
|
||||
private val _nodeFilterText = savedStateHandle.getStateFlow(KEY_FILTER_TEXT, "")
|
||||
private val includeUnknown = nodeFilterPreferences.includeUnknown
|
||||
private val excludeInfrastructure = nodeFilterPreferences.excludeInfrastructure
|
||||
private val onlyOnline = nodeFilterPreferences.onlyOnline
|
||||
private val onlyDirect = nodeFilterPreferences.onlyDirect
|
||||
private val showIgnored = nodeFilterPreferences.showIgnored
|
||||
private val moleculeScope = CoroutineScope(viewModelScope.coroutineContext + AndroidUiDispatcher.Main)
|
||||
val uiState: StateFlow<NodesUiState> by
|
||||
lazy(LazyThreadSafetyMode.NONE) {
|
||||
moleculeScope.launchMolecule(mode = RecompositionMode.ContextClock) {
|
||||
val filterText by filterText
|
||||
val includeUnknown by nodeFilterPreferences.includeUnknown.collectAsState()
|
||||
val excludeInfrastructure by nodeFilterPreferences.excludeInfrastructure.collectAsState()
|
||||
val onlyOnline by nodeFilterPreferences.onlyOnline.collectAsState()
|
||||
val onlyDirect by nodeFilterPreferences.onlyDirect.collectAsState()
|
||||
val showIgnored by nodeFilterPreferences.showIgnored.collectAsState()
|
||||
|
||||
private val nodeFilter: Flow<NodeFilterState> =
|
||||
combine(_nodeFilterText, includeUnknown, excludeInfrastructure, onlyOnline, onlyDirect, showIgnored) { values ->
|
||||
NodeFilterState(
|
||||
filterText = values[0] as String,
|
||||
includeUnknown = values[1] as Boolean,
|
||||
excludeInfrastructure = values[2] as Boolean,
|
||||
onlyOnline = values[3] as Boolean,
|
||||
onlyDirect = values[4] as Boolean,
|
||||
showIgnored = values[5] as Boolean,
|
||||
)
|
||||
}
|
||||
val nodesUiState: StateFlow<NodesUiState> =
|
||||
combine(nodeSortOption, nodeFilter, radioConfigRepository.deviceProfileFlow) { sort, nodeFilter, profile ->
|
||||
NodesUiState(
|
||||
sort = sort,
|
||||
filter = nodeFilter,
|
||||
distanceUnits = profile.config.display.units.number,
|
||||
tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit,
|
||||
)
|
||||
}
|
||||
.stateInWhileSubscribed(initialValue = NodesUiState())
|
||||
|
||||
val nodeList: StateFlow<List<Node>> =
|
||||
combine(nodeFilter, nodeSortOption, ::Pair)
|
||||
.flatMapLatest { (filter, sort) ->
|
||||
nodeRepository
|
||||
.getNodes(
|
||||
sort = sort,
|
||||
filter = filter.filterText,
|
||||
includeUnknown = filter.includeUnknown,
|
||||
onlyOnline = filter.onlyOnline,
|
||||
onlyDirect = filter.onlyDirect,
|
||||
val filterState =
|
||||
NodeFilterState(
|
||||
filterText = filterText,
|
||||
includeUnknown = includeUnknown,
|
||||
excludeInfrastructure = excludeInfrastructure,
|
||||
onlyOnline = onlyOnline,
|
||||
onlyDirect = onlyDirect,
|
||||
showIgnored = showIgnored,
|
||||
)
|
||||
.map { list ->
|
||||
list
|
||||
.filter { filter.showIgnored || !it.isIgnored }
|
||||
.filter { node ->
|
||||
if (filter.excludeInfrastructure) {
|
||||
val role = node.user.role
|
||||
val infrastructureRoles =
|
||||
listOf(
|
||||
ConfigProtos.Config.DeviceConfig.Role.ROUTER,
|
||||
ConfigProtos.Config.DeviceConfig.Role.REPEATER,
|
||||
ConfigProtos.Config.DeviceConfig.Role.ROUTER_LATE,
|
||||
ConfigProtos.Config.DeviceConfig.Role.CLIENT_BASE,
|
||||
)
|
||||
role !in infrastructureRoles && !node.isEffectivelyUnmessageable
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
val sort by
|
||||
nodeFilterPreferences.nodeSortOption
|
||||
.collectAsState(NodeSortOption.VIA_FAVORITE)
|
||||
val profile by radioConfigRepository.deviceProfileFlow.collectAsState(deviceProfile {})
|
||||
NodesUiState(
|
||||
sort = sort,
|
||||
filter = filterState,
|
||||
distanceUnits = profile.config.display.units.number,
|
||||
tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit,
|
||||
)
|
||||
}
|
||||
.stateInWhileSubscribed(initialValue = emptyList())
|
||||
}
|
||||
|
||||
val nodeList: StateFlow<List<Node>> by
|
||||
lazy(LazyThreadSafetyMode.NONE) {
|
||||
moleculeScope.launchMolecule(mode = RecompositionMode.ContextClock) {
|
||||
val uiState by uiState.collectAsState()
|
||||
val sort = uiState.sort
|
||||
val filter = uiState.filter
|
||||
|
||||
val nodeList by
|
||||
nodeRepository
|
||||
.getNodes(
|
||||
sort = sort,
|
||||
filter = filter.filterText,
|
||||
includeUnknown = filter.includeUnknown,
|
||||
onlyOnline = filter.onlyOnline,
|
||||
onlyDirect = filter.onlyDirect,
|
||||
)
|
||||
.map { list ->
|
||||
list
|
||||
.filter { filter.showIgnored || !it.isIgnored }
|
||||
.filter { node ->
|
||||
if (filter.excludeInfrastructure) {
|
||||
val role = node.user.role
|
||||
val infrastructureRoles =
|
||||
listOf(
|
||||
ConfigProtos.Config.DeviceConfig.Role.ROUTER,
|
||||
ConfigProtos.Config.DeviceConfig.Role.REPEATER,
|
||||
ConfigProtos.Config.DeviceConfig.Role.ROUTER_LATE,
|
||||
ConfigProtos.Config.DeviceConfig.Role.CLIENT_BASE,
|
||||
)
|
||||
role !in infrastructureRoles && !node.isEffectivelyUnmessageable
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
.collectAsState(emptyList())
|
||||
nodeList
|
||||
}
|
||||
}
|
||||
|
||||
val unfilteredNodeList: StateFlow<List<Node>> =
|
||||
nodeRepository.getNodes().stateInWhileSubscribed(initialValue = emptyList())
|
||||
|
||||
var nodeFilterText: String
|
||||
get() = _nodeFilterText.value
|
||||
set(value) {
|
||||
savedStateHandle[KEY_FILTER_TEXT] = value
|
||||
}
|
||||
fun setFilterText(filterText: String) {
|
||||
savedStateHandle[KEY_FILTER_TEXT] = value
|
||||
}
|
||||
|
||||
fun setSortOption(sort: NodeSortOption) {
|
||||
nodeFilterPreferences.setNodeSort(sort)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-
|
|||
markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-renderer-android", version.ref = "markdownRenderer" }
|
||||
material = { module = "com.google.android.material:material", version = "1.13.0" }
|
||||
mgrs = { module = "mil.nga:mgrs", version = "2.1.3" }
|
||||
molecule = { module = "app.cash.molecule:molecule-runtime", version = "2.2.0" }
|
||||
nordic = { module = "no.nordicsemi.kotlin.ble:client-android", version = "2.0.0-alpha12" }
|
||||
nordic-dfu = { module = "no.nordicsemi.android:dfu", version = "2.10.1" }
|
||||
org-eclipse-paho-client-mqttv3 = { module = "org.eclipse.paho:org.eclipse.paho.client.mqttv3", version = "1.2.5" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue