mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
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
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:
parent
9b8ac6a460
commit
553ca2f8ed
38 changed files with 705 additions and 515 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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?> =
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue