mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
The hardware information displayed in the NodeInfo view was incorrect. This commit fixes the issue by using the correct node information to retrieve the hardware model string.
334 lines
13 KiB
Kotlin
334 lines
13 KiB
Kotlin
package com.geeksville.mesh.ui
|
|
|
|
import androidx.compose.animation.animateColorAsState
|
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
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.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.material.Card
|
|
import androidx.compose.material.Chip
|
|
import androidx.compose.material.ChipDefaults
|
|
import androidx.compose.material.ExperimentalMaterialApi
|
|
import androidx.compose.material.LocalTextStyle
|
|
import androidx.compose.material.MaterialTheme
|
|
import androidx.compose.material.Surface
|
|
import androidx.compose.material.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.text.font.FontStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
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
|
|
import com.geeksville.mesh.R
|
|
import com.geeksville.mesh.ui.compose.ElevationInfo
|
|
import com.geeksville.mesh.ui.compose.SatelliteCountInfo
|
|
import com.geeksville.mesh.ui.preview.NodeInfoPreviewParameterProvider
|
|
import com.geeksville.mesh.ui.theme.AppTheme
|
|
import com.geeksville.mesh.util.metersIn
|
|
|
|
@OptIn(ExperimentalMaterialApi::class)
|
|
@Composable
|
|
fun NodeInfo(
|
|
thisNodeInfo: NodeInfo?,
|
|
thatNodeInfo: NodeInfo,
|
|
gpsFormat: Int,
|
|
distanceUnits: Int,
|
|
tempInFahrenheit: Boolean,
|
|
isIgnored: Boolean = false,
|
|
onClicked: () -> Unit = {},
|
|
blinking: Boolean = false,
|
|
) {
|
|
val unknownShortName = stringResource(id = R.string.unknown_node_short_name)
|
|
val unknownLongName = stringResource(id = R.string.unknown_username)
|
|
|
|
val nodeName = thatNodeInfo.user?.longName ?: unknownLongName
|
|
val isThisNode = thisNodeInfo?.num == thatNodeInfo.num
|
|
val distance = thisNodeInfo?.distanceStr(thatNodeInfo, distanceUnits)
|
|
val (textColor, nodeColor) = thatNodeInfo.colors
|
|
|
|
val highlight = Color(0x33FFFFFF)
|
|
val bgColor by animateColorAsState(
|
|
targetValue = if (blinking) highlight else Color.Transparent,
|
|
animationSpec = repeatable(
|
|
iterations = 6,
|
|
animation = tween(
|
|
durationMillis = 250,
|
|
easing = FastOutSlowInEasing
|
|
),
|
|
repeatMode = RepeatMode.Reverse
|
|
), label = "blinking node"
|
|
)
|
|
|
|
Card(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
.defaultMinSize(minHeight = 80.dp)
|
|
) {
|
|
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
|
|
modifier = Modifier
|
|
.height(32.dp)
|
|
.constrainAs(chip) {
|
|
top.linkTo(parent.top)
|
|
start.linkTo(parent.start)
|
|
}
|
|
) {
|
|
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,
|
|
)
|
|
width = Dimension.preferredWrapContent
|
|
},
|
|
satCount = satCount
|
|
)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
end.linkTo(parent.end)
|
|
},
|
|
text = envMetrics,
|
|
color = MaterialTheme.colors.onSurface,
|
|
fontSize = MaterialTheme.typography.button.fontSize
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
@Preview(showBackground = false)
|
|
fun NodeInfoSimplePreview() {
|
|
AppTheme {
|
|
val thisNodeInfo = NodeInfoPreviewParameterProvider().values.first()
|
|
val thatNodeInfo = NodeInfoPreviewParameterProvider().values.last()
|
|
NodeInfo(
|
|
thisNodeInfo = thisNodeInfo,
|
|
thatNodeInfo = thatNodeInfo,
|
|
1,
|
|
0,
|
|
true
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
@Preview(
|
|
showBackground = true,
|
|
uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES,
|
|
)
|
|
fun NodeInfoPreview(
|
|
@PreviewParameter(NodeInfoPreviewParameterProvider::class)
|
|
thatNodeInfo: NodeInfo
|
|
) {
|
|
AppTheme {
|
|
val thisNodeInfo = NodeInfoPreviewParameterProvider().values.first()
|
|
NodeInfo(
|
|
thisNodeInfo,
|
|
thatNodeInfo,
|
|
0,
|
|
1,
|
|
true
|
|
)
|
|
}
|
|
}
|