feat: implement global SnackbarManager and consolidate common UI setup (#4909)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-24 17:31:40 -05:00 committed by GitHub
parent 9b8ac6a460
commit 553ca2f8ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 705 additions and 515 deletions

View file

@ -26,8 +26,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -81,21 +79,11 @@ actual fun NodeDetailScreen(
) {
LaunchedEffect(nodeId) { viewModel.start(nodeId) }
val snackbarHostState = remember { SnackbarHostState() }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
if (effect is NodeRequestEffect.ShowFeedback) {
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
NodeDetailScaffold(
modifier = modifier,
uiState = uiState,
snackbarHostState = snackbarHostState,
viewModel = viewModel,
navigateToMessages = navigateToMessages,
onNavigate = onNavigate,
@ -109,7 +97,6 @@ actual fun NodeDetailScreen(
private fun NodeDetailScaffold(
modifier: Modifier,
uiState: NodeDetailUiState,
snackbarHostState: SnackbarHostState,
viewModel: NodeDetailViewModel,
navigateToMessages: (String) -> Unit,
onNavigate: (Route) -> Unit,
@ -139,7 +126,6 @@ private fun NodeDetailScaffold(
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->
NodeDetailContent(
uiState = uiState,

View file

@ -36,15 +36,11 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@ -64,7 +60,6 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.icon.Save
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.proto.Config
import org.meshtastic.proto.Position
@ -104,18 +99,6 @@ private fun ActionButtons(
@Composable
actual fun PositionLogScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
val exportPositionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@ -144,7 +127,6 @@ actual fun PositionLogScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Un
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
BoxWithConstraints(modifier = Modifier.padding(innerPadding)) {
val compactWidth = maxWidth < 600.dp

View file

@ -18,11 +18,8 @@ package org.meshtastic.feature.node.detail
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -46,12 +43,14 @@ import org.meshtastic.core.resources.requesting_from
import org.meshtastic.core.resources.signal_quality
import org.meshtastic.core.resources.traceroute
import org.meshtastic.core.resources.user_info
import org.meshtastic.core.ui.util.SnackbarManager
@Single(binds = [NodeRequestActions::class])
class CommonNodeRequestActions constructor(private val radioController: RadioController) : NodeRequestActions {
private val _effects = MutableSharedFlow<NodeRequestEffect>()
override val effects: SharedFlow<NodeRequestEffect> = _effects.asSharedFlow()
class CommonNodeRequestActions
constructor(
private val radioController: RadioController,
private val snackbarManager: SnackbarManager,
) : NodeRequestActions {
private val _lastTracerouteTime = MutableStateFlow<Long?>(null)
override val lastTracerouteTime: StateFlow<Long?> = _lastTracerouteTime.asStateFlow()
@ -59,15 +58,15 @@ class CommonNodeRequestActions constructor(private val radioController: RadioCon
private val _lastRequestNeighborTimes = MutableStateFlow<Map<Int, Long>>(emptyMap())
override val lastRequestNeighborTimes: StateFlow<Map<Int, Long>> = _lastRequestNeighborTimes.asStateFlow()
private suspend fun showFeedback(text: UiText) {
snackbarManager.showSnackbar(message = text.resolve())
}
override fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting UserInfo for '$destNum'" }
radioController.requestUserInfo(destNum)
_effects.emit(
NodeRequestEffect.ShowFeedback(
UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName),
),
)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName))
}
}
@ -77,11 +76,7 @@ class CommonNodeRequestActions constructor(private val radioController: RadioCon
val packetId = radioController.getPacketId()
radioController.requestNeighborInfo(packetId, destNum)
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
_effects.emit(
NodeRequestEffect.ShowFeedback(
UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName),
),
)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
}
}
@ -89,11 +84,7 @@ class CommonNodeRequestActions constructor(private val radioController: RadioCon
scope.launch(ioDispatcher) {
Logger.i { "Requesting position for '$destNum'" }
radioController.requestPosition(destNum, position)
_effects.emit(
NodeRequestEffect.ShowFeedback(
UiText.Resource(Res.string.requesting_from, Res.string.position, longName),
),
)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.position, longName))
}
}
@ -114,9 +105,7 @@ class CommonNodeRequestActions constructor(private val radioController: RadioCon
TelemetryType.PAX -> Res.string.request_pax_metrics
}
_effects.emit(
NodeRequestEffect.ShowFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName)),
)
showFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName))
}
}
@ -126,11 +115,7 @@ class CommonNodeRequestActions constructor(private val radioController: RadioCon
val packetId = radioController.getPacketId()
radioController.requestTraceroute(packetId, destNum)
_lastTracerouteTime.value = nowMillis
_effects.emit(
NodeRequestEffect.ShowFeedback(
UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName),
),
)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
}
}
}

View file

@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@ -84,8 +83,6 @@ class NodeDetailViewModel(
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), NodeDetailUiState())
val effects: SharedFlow<NodeRequestEffect> = nodeRequestActions.effects
fun start(nodeId: Int) {
if (manualNodeId.value != nodeId) {
manualNodeId.value = nodeId

View file

@ -17,19 +17,12 @@
package org.meshtastic.feature.node.detail
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.TelemetryType
import org.meshtastic.core.resources.UiText
sealed class NodeRequestEffect {
data class ShowFeedback(val text: UiText) : NodeRequestEffect()
}
/** Interface for high-level node request actions (e.g., requesting user info, position, telemetry). */
interface NodeRequestActions {
val effects: SharedFlow<NodeRequestEffect>
val lastTracerouteTime: StateFlow<Long?>
val lastRequestNeighborTimes: StateFlow<Map<Int, Long>>

View file

@ -30,8 +30,6 @@ import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -152,7 +150,6 @@ fun <T> BaseMetricScreen(
data: List<T>,
timeProvider: (T) -> Double,
infoData: List<InfoDialogData> = emptyList(),
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
onRequestTelemetry: (() -> Unit)? = null,
chartPart: @Composable (Modifier, Double?, VicoScrollState, (Double) -> Unit) -> Unit,
listPart: @Composable (Modifier, Double?, LazyListState, (Double) -> Unit) -> Unit,
@ -192,7 +189,6 @@ fun <T> BaseMetricScreen(
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
if (displayInfoDialog) {

View file

@ -36,7 +36,6 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -84,7 +83,6 @@ import org.meshtastic.core.ui.theme.GraphColors.Cyan
import org.meshtastic.core.ui.theme.GraphColors.Gold
import org.meshtastic.core.ui.theme.GraphColors.Green
import org.meshtastic.core.ui.theme.GraphColors.Purple
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.proto.Telemetry
@ -130,24 +128,12 @@ fun DeviceMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val timeFrame by viewModel.timeFrame.collectAsStateWithLifecycle()
val availableTimeFrames by viewModel.availableTimeFrames.collectAsStateWithLifecycle()
val data = state.deviceMetrics.filter { it.time.toLong() >= timeFrame.timeThreshold() }
val snackbarHostState = remember { SnackbarHostState() }
val hasBattery = remember(data) { data.any { it.device_metrics?.battery_level != null } }
val hasVoltage = remember(data) { data.any { it.device_metrics?.voltage != null } }
val hasChUtil = remember(data) { data.any { it.device_metrics?.channel_utilization != null } }
val hasAirUtil = remember(data) { data.any { it.device_metrics?.air_util_tx != null } }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
val filteredLegendData =
remember(hasBattery, hasVoltage, hasChUtil, hasAirUtil) {
LEGEND_DATA.filter { d ->
@ -193,7 +179,6 @@ fun DeviceMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
data = data,
timeProvider = { it.time.toDouble() },
infoData = infoItems,
snackbarHostState = snackbarHostState,
onRequestTelemetry = { viewModel.requestTelemetry(TelemetryType.DEVICE) },
controlPart = {
TimeFrameSelector(

View file

@ -35,13 +35,10 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -68,7 +65,6 @@ import org.meshtastic.core.resources.uv_lux
import org.meshtastic.core.resources.voltage
import org.meshtastic.core.ui.component.IaqDisplayMode
import org.meshtastic.core.ui.component.IndoorAirQuality
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.proto.Telemetry
@ -79,18 +75,6 @@ fun EnvironmentMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Un
val filteredTelemetries by viewModel.filteredEnvironmentMetrics.collectAsStateWithLifecycle()
val timeFrame by viewModel.timeFrame.collectAsStateWithLifecycle()
val availableTimeFrames by viewModel.availableTimeFrames.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
BaseMetricScreen(
onNavigateUp = onNavigateUp,
@ -100,7 +84,6 @@ fun EnvironmentMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Un
data = filteredTelemetries,
timeProvider = { it.time.toDouble() },
infoData = listOf(InfoDialogData(Res.string.iaq, Res.string.iaq_definition, Environment.IAQ.color)),
snackbarHostState = snackbarHostState,
onRequestTelemetry = { viewModel.requestTelemetry(TelemetryType.ENVIRONMENT) },
controlPart = {
TimeFrameSelector(

View file

@ -38,13 +38,9 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
@ -68,25 +64,12 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.icon.DataArray
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.proto.Telemetry
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HostMetricsLogScreen(metricsViewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val state by metricsViewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
metricsViewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
val hostMetrics = state.hostMetrics
@ -108,7 +91,6 @@ fun HostMetricsLogScreen(metricsViewModel: MetricsViewModel, onNavigateUp: () ->
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
LazyColumn(
modifier = Modifier.fillMaxSize().padding(innerPadding),

View file

@ -23,7 +23,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@ -64,7 +63,6 @@ import org.meshtastic.core.ui.util.toMessageRes
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.feature.map.model.TracerouteOverlay
import org.meshtastic.feature.node.detail.NodeRequestActions
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
import org.meshtastic.feature.node.model.MetricsState
import org.meshtastic.feature.node.model.TimeFrame
@ -175,8 +173,6 @@ open class MetricsViewModel(
}
.stateInWhileSubscribed(emptyList())
val effects: SharedFlow<NodeRequestEffect> = nodeRequestActions.effects
val lastTraceRouteTime: StateFlow<Long?> = nodeRequestActions.lastTracerouteTime
val lastRequestNeighborsTime: StateFlow<Long?> =

View file

@ -28,10 +28,7 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -55,25 +52,12 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
import org.meshtastic.core.ui.util.annotateNeighborInfo
import org.meshtastic.feature.node.component.CooldownIconButton
import org.meshtastic.feature.node.detail.NodeRequestEffect
@OptIn(ExperimentalFoundationApi::class)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun NeighborInfoLogScreen(modifier: Modifier = Modifier, viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
fun getUsername(nodeNum: Int): String = with(viewModel.getUser(nodeNum)) { "$long_name ($short_name)" }
@ -104,7 +88,6 @@ fun NeighborInfoLogScreen(modifier: Modifier = Modifier, viewModel: MetricsViewM
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
LazyColumn(
modifier = modifier.fillMaxSize().padding(innerPadding),

View file

@ -31,7 +31,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -70,7 +69,6 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Paxcount
import org.meshtastic.core.ui.theme.GraphColors.Orange
import org.meshtastic.core.ui.theme.GraphColors.Purple
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.proto.Paxcount as ProtoPaxcount
private enum class PaxSeries(val color: Color, val legendRes: StringResource) {
@ -177,18 +175,6 @@ fun PaxMetricsScreen(metricsViewModel: MetricsViewModel, onNavigateUp: () -> Uni
val paxMetrics by metricsViewModel.filteredPaxMetrics.collectAsStateWithLifecycle()
val timeFrame by metricsViewModel.timeFrame.collectAsStateWithLifecycle()
val availableTimeFrames by metricsViewModel.availableTimeFrames.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
metricsViewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
// Prepare data for graph
val graphData =
@ -211,7 +197,6 @@ fun PaxMetricsScreen(metricsViewModel: MetricsViewModel, onNavigateUp: () -> Uni
nodeName = state.node?.user?.long_name ?: "",
data = paxMetrics,
timeProvider = { (it.first.received_date / CommonCharts.MS_PER_SEC).toDouble() },
snackbarHostState = snackbarHostState,
onRequestTelemetry = { metricsViewModel.requestTelemetry(TelemetryType.PAX) },
controlPart = {
TimeFrameSelector(

View file

@ -36,7 +36,6 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -73,7 +72,6 @@ import org.meshtastic.core.resources.power_metrics_log
import org.meshtastic.core.resources.voltage
import org.meshtastic.core.ui.theme.GraphColors.Gold
import org.meshtastic.core.ui.theme.GraphColors.InfantryBlue
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.proto.Telemetry
@ -112,18 +110,6 @@ fun PowerMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val availableTimeFrames by viewModel.availableTimeFrames.collectAsStateWithLifecycle()
val data = state.powerMetrics.filter { it.time.toLong() >= timeFrame.timeThreshold() }
var selectedChannel by remember { mutableStateOf(PowerChannel.ONE) }
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
BaseMetricScreen(
onNavigateUp = onNavigateUp,
@ -132,7 +118,6 @@ fun PowerMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
nodeName = state.node?.user?.long_name ?: "",
data = data,
timeProvider = { it.time.toDouble() },
snackbarHostState = snackbarHostState,
onRequestTelemetry = { viewModel.requestTelemetry(TelemetryType.POWER) },
controlPart = {
Column {

View file

@ -35,7 +35,6 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -67,7 +66,6 @@ import org.meshtastic.core.resources.snr_definition
import org.meshtastic.core.ui.component.LoraSignalIndicator
import org.meshtastic.core.ui.theme.GraphColors.Blue
import org.meshtastic.core.ui.theme.GraphColors.Green
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.proto.MeshPacket
@ -89,18 +87,6 @@ fun SignalMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val timeFrame by viewModel.timeFrame.collectAsStateWithLifecycle()
val availableTimeFrames by viewModel.availableTimeFrames.collectAsStateWithLifecycle()
val data = state.signalMetrics.filter { it.rx_time.toLong() >= timeFrame.timeThreshold() }
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
BaseMetricScreen(
onNavigateUp = onNavigateUp,
@ -109,7 +95,6 @@ fun SignalMetricsScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
nodeName = state.node?.user?.long_name ?: "",
data = data,
timeProvider = { it.rx_time.toDouble() },
snackbarHostState = snackbarHostState,
onRequestTelemetry = { viewModel.requestTelemetry(TelemetryType.LOCAL_STATS) },
infoData =
listOf(

View file

@ -28,10 +28,7 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -72,7 +69,6 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
import org.meshtastic.core.ui.util.annotateTraceroute
import org.meshtastic.feature.map.model.TracerouteOverlay
import org.meshtastic.feature.node.component.CooldownIconButton
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.proto.RouteDiscovery
@OptIn(ExperimentalFoundationApi::class)
@ -85,18 +81,6 @@ fun TracerouteLogScreen(
onViewOnMap: (requestId: Int, responseLogUuid: String) -> Unit = { _, _ -> },
) {
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
when (effect) {
is NodeRequestEffect.ShowFeedback -> {
@Suppress("SpreadOperator")
snackbarHostState.showSnackbar(effect.text.resolve())
}
}
}
}
fun getUsername(nodeNum: Int): String = with(viewModel.getUser(nodeNum)) { "$long_name ($short_name)" }
@ -127,7 +111,6 @@ fun TracerouteLogScreen(
onClickChip = {},
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { innerPadding ->
LazyColumn(
modifier = modifier.fillMaxSize().padding(innerPadding),

View file

@ -57,7 +57,6 @@ class NodeDetailViewModelTest {
Dispatchers.setMain(testDispatcher)
every { getNodeDetailsUseCase(any()) } returns emptyFlow()
every { nodeRequestActions.effects } returns kotlinx.coroutines.flow.MutableSharedFlow()
viewModel = createViewModel(1234)
}

View file

@ -78,7 +78,6 @@ class MetricsViewModelTest {
// Default setup for flows
every { serviceRepository.tracerouteResponse } returns MutableStateFlow(null)
every { nodeRequestActions.effects } returns mock()
every { nodeRequestActions.lastTracerouteTime } returns MutableStateFlow(null)
every { nodeRequestActions.lastRequestNeighborTimes } returns MutableStateFlow(emptyMap())
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())