From fc2559f5f5b74d2e70306a1bc7a12cb5ac064675 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sun, 28 Jul 2024 05:04:50 -0500 Subject: [PATCH] Expanding node info cards (#1144) --- app/build.gradle | 1 - .../java/com/geeksville/mesh/model/UIState.kt | 26 +- .../geeksville/mesh/ui/LinkedCoordinates.kt | 35 +- .../java/com/geeksville/mesh/ui/NodeInfo.kt | 408 +++++++++--------- .../com/geeksville/mesh/ui/NodeSortButton.kt | 21 + .../com/geeksville/mesh/ui/UsersFragment.kt | 10 +- .../ui/preview/PreviewParameterProviders.kt | 2 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 268 insertions(+), 236 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 567fb41fd..2a2394122 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,7 +193,6 @@ dependencies { def composeBom = platform('androidx.compose:compose-bom:2024.06.00') implementation composeBom androidTestImplementation composeBom - implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" implementation 'androidx.compose.material:material' implementation 'androidx.activity:activity-compose' diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 095549dd7..1329f8f9d 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -12,16 +12,16 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.geeksville.mesh.android.Logging import com.geeksville.mesh.* import com.geeksville.mesh.ChannelProtos.ChannelSettings import com.geeksville.mesh.ConfigProtos.Config -import com.geeksville.mesh.database.MeshLogRepository -import com.geeksville.mesh.database.QuickChatActionRepository -import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig +import com.geeksville.mesh.android.Logging +import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.PacketRepository +import com.geeksville.mesh.database.QuickChatActionRepository +import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshService @@ -29,10 +29,10 @@ import com.geeksville.mesh.util.positionToMeter import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -102,10 +102,11 @@ data class NodesUiState( val sort: NodeSortOption = NodeSortOption.LAST_HEARD, val filter: String = "", val includeUnknown: Boolean = false, - val gpsFormat:Int = 0, - val distanceUnits:Int = 0, - val tempInFahrenheit:Boolean = false, + val gpsFormat: Int = 0, + val distanceUnits: Int = 0, + val tempInFahrenheit: Boolean = false, val ignoreIncomingList: List = emptyList(), + val showDetails: Boolean = false, ) { companion object { val Empty = NodesUiState() @@ -151,11 +152,16 @@ class UIViewModel @Inject constructor( private val nodeFilterText = MutableStateFlow("") private val nodeSortOption = MutableStateFlow(NodeSortOption.LAST_HEARD) private val includeUnknown = MutableStateFlow(false) + private val showDetails = MutableStateFlow(false) fun setSortOption(sort: NodeSortOption) { nodeSortOption.value = sort } + fun toggleShowDetails() { + showDetails.value = !showDetails.value + } + fun toggleIncludeUnknown() { includeUnknown.value = !includeUnknown.value } @@ -164,8 +170,9 @@ class UIViewModel @Inject constructor( nodeFilterText, nodeSortOption, includeUnknown, + showDetails, radioConfigRepository.deviceProfileFlow, - ) { filter, sort, includeUnknown, profile -> + ) { filter, sort, includeUnknown, showDetails, profile -> NodesUiState( sort = sort, filter = filter, @@ -174,6 +181,7 @@ class UIViewModel @Inject constructor( distanceUnits = profile.config.display.units.number, tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit, ignoreIncomingList = profile.config.lora.ignoreIncomingList, + showDetails = showDetails, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt b/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt index 74811f19a..d6e22c275 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt @@ -1,11 +1,15 @@ package com.geeksville.mesh.ui import android.content.ActivityNotFoundException +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.text.ClickableText import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -23,9 +27,10 @@ import com.geeksville.mesh.ui.theme.AppTheme import com.geeksville.mesh.ui.theme.HyperlinkBlue import java.net.URLEncoder +@OptIn(ExperimentalFoundationApi::class) @Composable fun LinkedCoordinates( - modifier : Modifier = Modifier, + modifier: Modifier = Modifier, position: Position?, format: Int, nodeName: String? @@ -52,20 +57,28 @@ fun LinkedCoordinates( } pop() } - ClickableText( - modifier = modifier, - text = annotatedString, - onClick = { offset -> - debug("Clicked on link") - annotatedString.getStringAnnotations(tag = "gps", start = offset, end = offset) - .firstOrNull()?.let { + val clipboardManager: ClipboardManager = LocalClipboardManager.current + Text( + modifier = modifier.combinedClickable( + onClick = { + annotatedString.getStringAnnotations( + tag = "gps", + start = 0, + end = annotatedString.length + ).firstOrNull()?.let { try { uriHandler.openUri(it.item) } catch (ex: ActivityNotFoundException) { debug("No application found: $ex") } } - } + }, + onLongClick = { + clipboardManager.setText(annotatedString) + debug("Copied to clipboard") + } + ), + text = annotatedString ) } else { // Placeholder for ConstraintLayoutReference; renders no visible content @@ -99,7 +112,7 @@ fun LinkedCoordinatesPreview( } } -class GPSFormatPreviewParameterProvider: PreviewParameterProvider { +class GPSFormatPreviewParameterProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf(0, 1, 2) } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt index afee480b1..cf350caae 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt @@ -1,3 +1,12 @@ +@file:Suppress( + "FunctionNaming", + "LongMethod", + "LongParameterList", + "DestructuringDeclarationWithTooManyEntries", + "MagicNumber", + "CyclomaticComplexMethod", +) + package com.geeksville.mesh.ui import androidx.compose.animation.animateColorAsState @@ -6,15 +15,21 @@ import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.repeatable import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.selection.DisableSelection +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Card import androidx.compose.material.Chip import androidx.compose.material.ChipDefaults +import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme @@ -22,6 +37,9 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -32,8 +50,6 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.NodeInfo @@ -53,8 +69,9 @@ fun NodeInfo( distanceUnits: Int, tempInFahrenheit: Boolean, isIgnored: Boolean = false, - onClicked: () -> Unit = {}, + chipClicked: () -> Unit = {}, blinking: Boolean = false, + expanded: Boolean = false, ) { val unknownShortName = stringResource(id = R.string.unknown_node_short_name) val unknownLongName = stringResource(id = R.string.unknown_username) @@ -64,6 +81,9 @@ fun NodeInfo( val distance = thisNodeInfo?.distanceStr(thatNodeInfo, distanceUnits) val (textColor, nodeColor) = thatNodeInfo.colors + val position = thatNodeInfo.position + val hwInfoString = thatNodeInfo.user?.hwModelString + val highlight = Color(0x33FFFFFF) val bgColor by animateColorAsState( targetValue = if (blinking) highlight else Color.Transparent, @@ -74,222 +94,167 @@ fun NodeInfo( easing = FastOutSlowInEasing ), repeatMode = RepeatMode.Reverse - ), label = "blinking node" + ), + label = "blinking node" ) + val style = if (thatNodeInfo.user?.hwModel == MeshProtos.HardwareModel.UNSET) { + LocalTextStyle.current.copy(fontStyle = FontStyle.Italic) + } else { + LocalTextStyle.current + } + + val (detailsShown, showDetails) = remember { mutableStateOf(expanded) } + Card( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 4.dp) - .defaultMinSize(minHeight = 80.dp) + .defaultMinSize(minHeight = 80.dp), + onClick = { showDetails(!detailsShown) }, ) { Surface { - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .background(bgColor) - .padding(8.dp) - ) { - val (chip, dist, name, hw, pos, alt, sats, batt, heard, sig, env) = createRefs() - val barrierBattHeard = createStartBarrier(batt, heard) - val sigBarrier = createBottomBarrier(pos, heard) - - Box( - // removes the extra spacing above the chip + SelectionContainer { + Column( modifier = Modifier - .height(32.dp) - .constrainAs(chip) { - top.linkTo(parent.top) - start.linkTo(parent.start) - } + .fillMaxWidth() + .padding(8.dp) + .background(bgColor) ) { - Chip( - modifier = Modifier.width(72.dp), - onClick = onClicked, - colors = ChipDefaults.chipColors( - backgroundColor = Color(nodeColor), - contentColor = Color(textColor) - ), - content = { - Text( - modifier = Modifier.fillMaxWidth(), - text = thatNodeInfo.user?.shortName ?: unknownShortName, - fontWeight = FontWeight.Normal, - fontSize = MaterialTheme.typography.button.fontSize, - textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, - textAlign = TextAlign.Center, - ) - }, - ) - } - - if (distance != null) { - Text( - modifier = Modifier.constrainAs(dist) { - top.linkTo(chip.bottom, 8.dp) - start.linkTo(chip.start) - end.linkTo(chip.end) - }, - text = distance, - fontSize = MaterialTheme.typography.button.fontSize, - ) - } - - val style = if (thatNodeInfo.user?.hwModel == MeshProtos.HardwareModel.UNSET) { - LocalTextStyle.current.copy(fontStyle = FontStyle.Italic) - } else { - LocalTextStyle.current - } - Text( - modifier = Modifier.constrainAs(name) { - top.linkTo(parent.top) - linkTo( - start = chip.end, - end = barrierBattHeard, - bias = 0F, - startMargin = 8.dp, - endMargin = 8.dp, - ) - width = Dimension.preferredWrapContent - }, - text = nodeName, - style = style, - textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, - ) - - val hwInfoString = thatNodeInfo.user?.hwModelString - if (hwInfoString != null){ - Text( - modifier = Modifier.constrainAs(hw) { - linkTo( - top = name.bottom, - bottom = pos.top, - bias = 0F, - topMargin = 4.dp, - bottomMargin = 4.dp - ) - linkTo( - start = name.start, - end = barrierBattHeard, - bias = 0F, - endMargin = 8.dp - ) - width = Dimension.preferredWrapContent - }, - text = hwInfoString, - fontSize = MaterialTheme.typography.caption.fontSize, - style = style, - ) - } - - val position = thatNodeInfo.position - LinkedCoordinates( - modifier = Modifier.constrainAs(pos) { - linkTo( - top = hw.bottom, - bottom = sig.top, - bias = 0F, - topMargin = 4.dp, - bottomMargin = 4.dp - ) - linkTo( - start = name.start, - end = barrierBattHeard, - bias = 0F, - endMargin = 8.dp - ) - width = Dimension.preferredWrapContent - }, - position = position, - format = gpsFormat, - nodeName = nodeName - ) - - val signalShown = signalInfo( - modifier = Modifier.constrainAs(sig) { - top.linkTo(sigBarrier, 4.dp) - bottom.linkTo(env.top, 4.dp) - end.linkTo(parent.end) - }, - nodeInfo = thatNodeInfo, - isThisNode = isThisNode - ) - - if (position?.isValid() == true) { - val system = ConfigProtos.Config.DisplayConfig.DisplayUnits.forNumber(distanceUnits) - val altitude = position.altitude.metersIn(system) - val elevationSuffix = stringResource(id = R.string.elevation_suffix) - - ElevationInfo( - modifier = Modifier.constrainAs(alt) { - top.linkTo(pos.bottom, 4.dp) - if (signalShown) { - baseline.linkTo(sig.baseline) - } - linkTo( - start = pos.start, - end = sig.start, - endMargin = 8.dp, - bias = 0F, - ) - width = Dimension.preferredWrapContent - }, - altitude = altitude, - system = system, - suffix = elevationSuffix - ) - - val satCount = position.satellitesInView - if (satCount > 0) { - SatelliteCountInfo( - modifier = Modifier.constrainAs(sats) { - top.linkTo(alt.bottom, 4.dp) - linkTo( - start = pos.start, - end = env.start, - endMargin = 8.dp, - bias = 0F, + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Chip( + modifier = Modifier + .width(72.dp) + .padding(end = 8.dp) + .defaultMinSize(minHeight = 32.dp), + colors = ChipDefaults.chipColors( + backgroundColor = Color(nodeColor), + contentColor = Color(textColor) + ), + onClick = { chipClicked() }, + content = { + Text( + modifier = Modifier.fillMaxWidth(), + text = thatNodeInfo.user?.shortName ?: unknownShortName, + fontWeight = FontWeight.Normal, + fontSize = MaterialTheme.typography.button.fontSize, + textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, + textAlign = TextAlign.Center, ) - width = Dimension.preferredWrapContent }, - satCount = satCount + ) + Text( + modifier = Modifier.weight(1f), + text = nodeName, + style = style, + textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, + softWrap = true, + ) + + LastHeardInfo( + lastHeard = thatNodeInfo.lastHeard ) } - } - - BatteryInfo( - modifier = Modifier.constrainAs(batt) { - top.linkTo(parent.top) - end.linkTo(parent.end) - }, - batteryLevel = thatNodeInfo.batteryLevel, - voltage = thatNodeInfo.voltage - ) - - LastHeardInfo( - modifier = Modifier.constrainAs(heard) { - top.linkTo(batt.bottom, 4.dp) - end.linkTo(parent.end) - }, - lastHeard = thatNodeInfo.lastHeard - ) - - val envMetrics = thatNodeInfo.environmentMetrics - ?.getDisplayString(tempInFahrenheit) ?: "" - if (envMetrics.isNotBlank()) { - Text( - modifier = Modifier.constrainAs(env) { - if (signalShown) { - top.linkTo(sig.bottom, 4.dp) - } else { - top.linkTo(pos.bottom, 4.dp) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (distance != null) { + Text( + text = distance, + fontSize = MaterialTheme.typography.button.fontSize, + ) + } else { + Spacer(modifier = Modifier.width(16.dp)) + } + BatteryInfo( + batteryLevel = thatNodeInfo.batteryLevel, + voltage = thatNodeInfo.voltage + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + signalInfo( + nodeInfo = thatNodeInfo, + isThisNode = isThisNode + ) + if (position?.isValid() == true) { + val satCount = position.satellitesInView + if (satCount > 0) { + SatelliteCountInfo( + satCount = satCount + ) } - end.linkTo(parent.end) - }, - text = envMetrics, - color = MaterialTheme.colors.onSurface, - fontSize = MaterialTheme.typography.button.fontSize - ) + } + } + Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + thatNodeInfo.environmentMetrics?.getDisplayString(tempInFahrenheit)?.let { envMetrics -> + Text( + text = envMetrics, + color = MaterialTheme.colors.onSurface, + fontSize = MaterialTheme.typography.button.fontSize + ) + } + } + + if (detailsShown || expanded) { + Spacer(modifier = Modifier.height(8.dp)) + Divider() + Spacer(modifier = Modifier.height(8.dp)) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + DisableSelection { + LinkedCoordinates( + position = position, + format = gpsFormat, + nodeName = nodeName + ) + } + val system = + ConfigProtos.Config.DisplayConfig.DisplayUnits.forNumber(distanceUnits) + if (position?.isValid() == true) { + val altitude = position.altitude.metersIn(system) + val elevationSuffix = stringResource(id = R.string.elevation_suffix) + ElevationInfo( + altitude = altitude, + system = system, + suffix = elevationSuffix + ) + } + } + Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (hwInfoString != null) { + Text( + text = "$hwInfoString", + fontSize = MaterialTheme.typography.button.fontSize, + style = style, + ) + } + val nodeId = thatNodeInfo.user?.id + if (nodeId != null) { + Text(text = nodeId, fontSize = MaterialTheme.typography.button.fontSize) + } + } + } } } } @@ -323,12 +288,31 @@ fun NodeInfoPreview( ) { AppTheme { val thisNodeInfo = NodeInfoPreviewParameterProvider().values.first() - NodeInfo( - thisNodeInfo, - thatNodeInfo, - 0, - 1, - true - ) + Column { + Text( + text = "Details Collapsed", + color = MaterialTheme.colors.onBackground + ) + NodeInfo( + thisNodeInfo = thisNodeInfo, + thatNodeInfo = thatNodeInfo, + gpsFormat = 0, + distanceUnits = 1, + tempInFahrenheit = true, + expanded = false + ) + Text( + text = "Details Shown", + color = MaterialTheme.colors.onBackground + ) + NodeInfo( + thisNodeInfo = thisNodeInfo, + thatNodeInfo = thatNodeInfo, + gpsFormat = 0, + distanceUnits = 1, + tempInFahrenheit = true, + expanded = true + ) + } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeSortButton.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeSortButton.kt index 93ba95060..dacaef8fa 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeSortButton.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeSortButton.kt @@ -28,12 +28,15 @@ import androidx.compose.ui.unit.dp import com.geeksville.mesh.R import com.geeksville.mesh.model.NodeSortOption +@Suppress("LongMethod") @Composable internal fun NodeSortButton( currentSortOption: NodeSortOption, onSortSelected: (NodeSortOption) -> Unit, includeUnknown: Boolean, onToggleIncludeUnknown: () -> Unit, + showDetails: Boolean, + onToggleShowDetails: () -> Unit, modifier: Modifier = Modifier, ) { Box(modifier) { @@ -84,6 +87,24 @@ internal fun NodeSortButton( ) } } + Divider() + DropdownMenuItem( + onClick = { + onToggleShowDetails() + expanded = false + }, + ) { + Text( + text = stringResource(id = R.string.node_filter_show_details), + ) + AnimatedVisibility(visible = showDetails) { + Icon( + imageVector = Icons.Default.Done, + contentDescription = null, + modifier = Modifier.padding(start = 4.dp), + ) + } + } } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index b686bf6aa..c22464801 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -96,6 +96,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { parentFragmentManager.navigateToRadioConfig(node.num) } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -105,7 +106,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { AppTheme { - NodesScreen(model = model, onClick = ::popup) + NodesScreen(model = model, chipClicked = ::popup) } } } @@ -116,7 +117,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { @Composable fun NodesScreen( model: UIViewModel = hiltViewModel(), - onClick: (NodeInfo) -> Unit, + chipClicked: (NodeInfo) -> Unit, ) { val state by model.nodesUiState.collectAsStateWithLifecycle() @@ -156,6 +157,8 @@ fun NodesScreen( onSortSelected = model::setSortOption, includeUnknown = state.includeUnknown, onToggleIncludeUnknown = model::toggleIncludeUnknown, + showDetails = state.showDetails, + onToggleShowDetails = model::toggleShowDetails, ) } } @@ -168,8 +171,9 @@ fun NodesScreen( distanceUnits = state.distanceUnits, tempInFahrenheit = state.tempInFahrenheit, isIgnored = state.ignoreIncomingList.contains(node.num), - onClicked = { onClick(node) }, + chipClicked = { chipClicked(node) }, blinking = node == focusedNode, + expanded = state.showDetails ) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/preview/PreviewParameterProviders.kt b/app/src/main/java/com/geeksville/mesh/ui/preview/PreviewParameterProviders.kt index 3150ec780..af68a6d2e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/preview/PreviewParameterProviders.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/preview/PreviewParameterProviders.kt @@ -19,6 +19,7 @@ class NodeInfoPreviewParameterProvider: PreviewParameterProvider { latitude = 33.812511, longitude = -117.918976, altitude = 138, + satellitesInView = 4, ), lastHeard = currentTime(), channel = 0, @@ -60,6 +61,7 @@ class NodeInfoPreviewParameterProvider: PreviewParameterProvider { latitude = 33.80523471893125, longitude = -117.92084605996297, altitude = 121, + satellitesInView = 66, ), lastHeard = currentTime() - 300, channel = 0, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06b6c9401..8896db0a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Filter clear node filter Include unknown + Show details A-Z Channel Distance