mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Expanding node info cards (#1144)
This commit is contained in:
parent
ed17ae0734
commit
fc2559f5f5
8 changed files with 268 additions and 236 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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<Int> = 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,
|
||||
|
|
|
|||
|
|
@ -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<Int> {
|
||||
class GPSFormatPreviewParameterProvider : PreviewParameterProvider<Int> {
|
||||
override val values: Sequence<Int>
|
||||
get() = sequenceOf(0, 1, 2)
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class NodeInfoPreviewParameterProvider: PreviewParameterProvider<NodeInfo> {
|
|||
latitude = 33.812511,
|
||||
longitude = -117.918976,
|
||||
altitude = 138,
|
||||
satellitesInView = 4,
|
||||
),
|
||||
lastHeard = currentTime(),
|
||||
channel = 0,
|
||||
|
|
@ -60,6 +61,7 @@ class NodeInfoPreviewParameterProvider: PreviewParameterProvider<NodeInfo> {
|
|||
latitude = 33.80523471893125,
|
||||
longitude = -117.92084605996297,
|
||||
altitude = 121,
|
||||
satellitesInView = 66,
|
||||
),
|
||||
lastHeard = currentTime() - 300,
|
||||
channel = 0,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<string name="node_filter_placeholder">Filter</string>
|
||||
<string name="desc_node_filter_clear">clear node filter</string>
|
||||
<string name="node_filter_include_unknown">Include unknown</string>
|
||||
<string name="node_filter_show_details">Show details</string>
|
||||
<string name="node_sort_alpha">A-Z</string>
|
||||
<string name="node_sort_channel">Channel</string>
|
||||
<string name="node_sort_distance">Distance</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue