diff --git a/app/build.gradle b/app/build.gradle index 761e8654f..344956d01 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -194,6 +194,7 @@ dependencies { def composeBom = platform('androidx.compose:compose-bom:2024.02.01') 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/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index bf3ace02e..729e7b187 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -179,6 +179,32 @@ data class EnvironmentMetrics( override fun toString(): String { return "EnvironmentMetrics(time=${time}, temperature=${temperature}, humidity=${relativeHumidity}, pressure=${barometricPressure}), resistance=${gasResistance}, voltage=${voltage}, current=${current}" } + + fun getDisplayString(inFahrenheit: Boolean = false): String { + val temp = if (temperature != 0f) { + if (inFahrenheit) { + val fahrenheit = temperature * 1.8F + 32 + String.format("%.1f°F", fahrenheit) + } else { + String.format("%.1f°C", temperature) + } + } else null + val humidity = if (relativeHumidity != 0f) String.format("%.0f%%", relativeHumidity) else null + val pressure = if (barometricPressure != 0f) String.format("%.1fhPa", barometricPressure) else null + val gas = if (gasResistance != 0f) String.format("%.0fMΩ", gasResistance) else null + val voltage = if (voltage != 0f) String.format("%.2fV", voltage) else null + val current = if (current != 0f) String.format("%.1fmA", current) else null + + return listOfNotNull( + temp, + humidity, + pressure, + gas, + voltage, + current + ).joinToString(" ") + } + } @Parcelize @@ -213,22 +239,6 @@ data class NodeInfo( val voltage get() = deviceMetrics?.voltage val batteryStr get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else "" - private fun Float.envFormat(unit: String, decimalPlaces: Int = 1): String = - if (this != 0f) String.format("%.${decimalPlaces}f$unit", this) else "" - - fun envMetricStr(isFahrenheit: Boolean = false): String = buildString { - val env = environmentMetrics ?: return "" - if (env.temperature != 0f) append( - if (!isFahrenheit) env.temperature.envFormat("°C ") - else (env.temperature * 1.8f + 32).envFormat("°F ") - ) - append(env.relativeHumidity.envFormat("%% ", 0)) - append(env.barometricPressure.envFormat("hPa ")) - append(env.gasResistance.envFormat("MΩ ", 0)) - append(env.voltage.envFormat("V ", 2)) - append(env.current.envFormat("mA")) - } - /** * true if the device was heard from recently */ diff --git a/app/src/main/java/com/geeksville/mesh/ui/BatteryInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/BatteryInfo.kt index 8462f6c50..1c1f96628 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/BatteryInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/BatteryInfo.kt @@ -19,7 +19,11 @@ import com.geeksville.mesh.R import com.geeksville.mesh.ui.theme.AppTheme @Composable -fun BatteryInfo(batteryLevel: Int?, voltage: Float?) { +fun BatteryInfo( + modifier: Modifier = Modifier, + batteryLevel: Int?, + voltage: Float? +) { val infoString = "%d%% %.1fV".format(batteryLevel, voltage) val (image, level) = when (batteryLevel) { in 0 .. 4 -> R.drawable.ic_battery_alert to " $infoString" @@ -32,6 +36,7 @@ fun BatteryInfo(batteryLevel: Int?, voltage: Float?) { } Row( + modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { Icon( @@ -56,7 +61,10 @@ fun BatteryInfoPreview( batteryInfo: Pair ) { AppTheme { - BatteryInfo(batteryInfo.first, batteryInfo.second) + BatteryInfo( + batteryLevel = batteryInfo.first, + voltage = batteryInfo.second + ) } } @@ -64,7 +72,10 @@ fun BatteryInfoPreview( @Preview fun BatteryInfoPreviewSimple() { AppTheme { - BatteryInfo(85, 3.7F) + BatteryInfo( + batteryLevel = 85, + voltage = 3.7F + ) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/LastHeardInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/LastHeardInfo.kt index 4fe38330e..b494d97eb 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/LastHeardInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LastHeardInfo.kt @@ -19,9 +19,11 @@ import com.geeksville.mesh.util.formatAgo @Composable fun LastHeardInfo( + modifier: Modifier = Modifier, lastHeard: Int ) { Row( + modifier = modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(2.dp) ) { @@ -44,6 +46,6 @@ fun LastHeardInfo( @Preview(showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) fun LastHeardInfoPreview() { AppTheme { - LastHeardInfo((System.currentTimeMillis() / 1000).toInt() - 8600) + LastHeardInfo(lastHeard = (System.currentTimeMillis() / 1000).toInt() - 8600) } } \ No newline at end of file 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 2071a9e1a..b64da7be1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt @@ -3,6 +3,7 @@ package com.geeksville.mesh.ui import androidx.compose.foundation.text.ClickableText import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -21,11 +22,12 @@ import java.net.URLEncoder @Composable fun LinkedCoordinates( + modifier : Modifier = Modifier, position: Position?, format: Int, nodeName: String? ) { - if (position != null) { + if (position?.isValid() == true) { val uriHandler = LocalUriHandler.current val style = SpanStyle( color = HyperlinkBlue, @@ -46,8 +48,8 @@ fun LinkedCoordinates( pop() } ClickableText( + modifier = modifier, text = annotatedString, - maxLines = 1, onClick = { offset -> debug("Clicked on link") annotatedString.getStringAnnotations(tag = "gps", start = offset, end = offset) diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt new file mode 100644 index 000000000..4f622a627 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt @@ -0,0 +1,248 @@ +package com.geeksville.mesh.ui + +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.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +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.NodeInfo +import com.geeksville.mesh.R +import com.geeksville.mesh.ui.preview.NodeInfoPreviewParameterProvider +import com.geeksville.mesh.ui.theme.AppTheme + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun NodeInfo( + thisNodeInfo: NodeInfo, + thatNodeInfo: NodeInfo, + gpsFormat: Int, + distanceUnits: Int, + tempInFahrenheit: Boolean, + isIgnored: Boolean = false, + onClicked: () -> Unit = {} +) { + 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 + + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .defaultMinSize(minHeight = 80.dp) + ) { + Surface { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + val (chip, dist, name, pos, 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).strikeIf(isIgnored), + fontWeight = FontWeight.Normal, + fontSize = MaterialTheme.typography.button.fontSize, + 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 (nodeName == unknownLongName) { + 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.strikeIf(isIgnored), + style = style + ) + + LinkedCoordinates( + modifier = Modifier.constrainAs(pos) { + linkTo( + top = name.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 = thatNodeInfo.position, + format = gpsFormat, + nodeName = nodeName + ) + + 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 + ) + + SignalInfo( + modifier = Modifier.constrainAs(sig) { + top.linkTo(sigBarrier, 4.dp) + bottom.linkTo(env.top, 4.dp) + end.linkTo(parent.end) + }, + nodeInfo = thatNodeInfo, + isThisNode = isThisNode + ) + + val envMetrics = thatNodeInfo.environmentMetrics + ?.getDisplayString(tempInFahrenheit) ?: "" + if (envMetrics.isNotBlank()) { + Text( + modifier = Modifier.constrainAs(env) { + top.linkTo(sig.bottom, 4.dp) + end.linkTo(parent.end) + }, + text = envMetrics, + color = MaterialTheme.colors.onSurface, + fontSize = MaterialTheme.typography.button.fontSize + ) + } + } + } + } +} + +private fun String.strike() = AnnotatedString( + this, + spanStyles = listOf( + AnnotatedString.Range( + SpanStyle(textDecoration = TextDecoration.LineThrough), + start = 0, + end = this.length + ) + ) +) +private fun String.strikeIf(isIgnored: Boolean): AnnotatedString = if (isIgnored) strike() else AnnotatedString(this) + +@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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/SignalInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/SignalInfo.kt index c092a3dd6..9c780356d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SignalInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SignalInfo.kt @@ -3,6 +3,7 @@ package com.geeksville.mesh.ui import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import com.geeksville.mesh.NodeInfo @@ -11,6 +12,7 @@ import com.geeksville.mesh.ui.theme.AppTheme @Composable fun SignalInfo( + modifier: Modifier = Modifier, nodeInfo: NodeInfo, isThisNode: Boolean ) { @@ -30,6 +32,7 @@ fun SignalInfo( } if (text.isNotEmpty()) { Text( + modifier = modifier, text = text, color = MaterialTheme.colors.onSurface, fontSize = MaterialTheme.typography.button.fontSize @@ -41,16 +44,19 @@ fun SignalInfo( @Preview(showBackground = true) fun SignalInfoSimplePreview() { AppTheme { - SignalInfo(NodeInfo( - num = 1, - position = null, - lastHeard = 0, - channel = 0, - snr = 12.5F, - rssi = -42, - deviceMetrics = null, - user = null - ), false) + SignalInfo( + nodeInfo = NodeInfo( + num = 1, + position = null, + lastHeard = 0, + channel = 0, + snr = 12.5F, + rssi = -42, + deviceMetrics = null, + user = null + ), + isThisNode = false + ) } } @@ -62,7 +68,10 @@ fun SignalInfoPreview( nodeInfo: NodeInfo ) { AppTheme { - SignalInfo(nodeInfo, false) + SignalInfo( + nodeInfo = nodeInfo, + isThisNode = false + ) } } @@ -74,6 +83,9 @@ fun SignalInfoSelfPreview( nodeInfo: NodeInfo ) { AppTheme { - SignalInfo(nodeInfo, true) + SignalInfo( + nodeInfo = nodeInfo, + isThisNode = true + ) } } \ No newline at end of file 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 6313fcf84..2f44db28e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -12,6 +12,7 @@ import android.view.View import android.view.ViewGroup import android.view.animation.LinearInterpolator import androidx.appcompat.widget.PopupMenu +import androidx.compose.ui.platform.ComposeView import androidx.core.animation.doOnEnd import androidx.fragment.app.activityViewModels import androidx.lifecycle.asLiveData @@ -20,10 +21,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.geeksville.mesh.NodeInfo -import com.geeksville.mesh.Position import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.NodelistFragmentBinding import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.theme.AppTheme @@ -48,21 +47,11 @@ class UsersFragment : ScreenFragment("Users"), Logging { private var displayUnits = 0 private var displayFahrenheit = false - // Provide a direct reference to each of the views within a data item - // Used to cache the views within the item layout for fast access - class ViewHolder(itemView: AdapterNodeLayoutBinding) : RecyclerView.ViewHolder(itemView.root) { - val chipNode = itemView.chipNode - val nodeNameView = itemView.nodeNameView - val distanceView = itemView.distanceView - val envMetrics = itemView.envMetrics - val background = itemView.nodeCard - val nodePosition = itemView.nodePosition - val batteryInfo = itemView.batteryInfo - val lastHeard = itemView.lastHeardInfo - val signalInfo = itemView.signalInfo + class ViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView) { + // TODO not working with compose changes fun blink() { - val bg = background.backgroundTintList + val bg = composeView.backgroundTintList ValueAnimator.ofArgb( Color.parseColor("#00FFFFFF"), Color.parseColor("#33FFFFFF") @@ -73,38 +62,33 @@ class UsersFragment : ScreenFragment("Users"), Logging { repeatCount = 3 repeatMode = ValueAnimator.REVERSE addUpdateListener { - background.backgroundTintList = ColorStateList.valueOf(it.animatedValue as Int) + composeView.backgroundTintList = ColorStateList.valueOf(it.animatedValue as Int) } start() doOnEnd { - background.backgroundTintList = bg + composeView.backgroundTintList = bg } } } fun bind( - nodeInfo: NodeInfo, - isThisNode: Boolean, + thisNodeInfo: NodeInfo, + thatNodeInfo: NodeInfo, gpsFormat: Int, + distanceUnits: Int, + tempInFahrenheit: Boolean, + onChipClicked: () -> Unit ) { - batteryInfo.setContent { + composeView.setContent { AppTheme { - BatteryInfo(nodeInfo.batteryLevel, nodeInfo.voltage) - } - } - nodePosition.setContent { - AppTheme { - LinkedCoordinates(nodeInfo.validPosition, gpsFormat, nodeInfo.user?.longName) - } - } - this.lastHeard.setContent { - AppTheme { - LastHeardInfo(nodeInfo.lastHeard) - } - } - this.signalInfo.setContent { - AppTheme { - SignalInfo(nodeInfo, isThisNode) + NodeInfo( + thisNodeInfo = thisNodeInfo, + thatNodeInfo = thatNodeInfo, + gpsFormat = gpsFormat, + distanceUnits = distanceUnits, + tempInFahrenheit = tempInFahrenheit, + onClicked = onChipClicked + ) } } } @@ -213,13 +197,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { * @see .onBindViewHolder */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(requireContext()) - - // Inflate the custom layout - val contactView = AdapterNodeLayoutBinding.inflate(inflater, parent, false) - - // Return a new holder instance - return ViewHolder(contactView) + return ViewHolder(ComposeView(parent.context)) } /** @@ -251,53 +229,40 @@ class UsersFragment : ScreenFragment("Users"), Logging { * @param position The position of the item within the adapter's data set. */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val n = nodes[position] - val (textColor, nodeColor) = n.colors - val isIgnored: Boolean = ignoreIncomingList.contains(n.num) - val isThisNode = n.num == nodes[0].num + val thisNode = nodes[0] + val thatNode = nodes[position] - holder.bind(n, isThisNode, gpsFormat) - - holder.nodeNameView.text = n.user?.longName - - with(holder.chipNode) { - text = (n.user?.shortName ?: "UNK").strikeIf(isIgnored) - chipBackgroundColor = ColorStateList.valueOf(nodeColor) - setTextColor(textColor) - } - - val distance = nodes[0].distanceStr(n, displayUnits) - if (distance != null) { - holder.distanceView.text = distance - holder.distanceView.visibility = View.VISIBLE - } else { - holder.distanceView.visibility = View.INVISIBLE - } - - val envMetrics = n.envMetricStr(displayFahrenheit) - if (envMetrics.isNotEmpty()) { - holder.envMetrics.text = envMetrics - holder.envMetrics.visibility = View.VISIBLE - } else { - holder.envMetrics.visibility = View.GONE - } - - holder.chipNode.setOnClickListener { - popup(it, position) - } - holder.itemView.setOnLongClickListener { - popup(it, position) - true + holder.bind( + thisNodeInfo = thisNode, + thatNodeInfo = thatNode, + gpsFormat = gpsFormat, + distanceUnits = displayUnits, + tempInFahrenheit = displayFahrenheit + ) { + popup(holder.composeView, position) } } - /// Called when our node DB changes + // Called when our node DB changes fun onNodesChanged(nodesIn: Array) { - if (nodesIn.size > 1) + if (nodesIn.size > 1) { nodesIn.sortWith(compareByDescending { it.lastHeard }, 1) + } + + val previousNodes = nodes + val indexChanged = nodesIn.mapIndexed { index, nodeInfo -> + previousNodes.getOrNull(index) != nodeInfo + } + if (indexChanged.isEmpty()) return + nodes = nodesIn - notifyDataSetChanged() // FIXME, this is super expensive and redraws all nodes + for (i in indexChanged.indices) { + if (indexChanged[i]) { + notifyItemChanged(i) + } + } } + } override fun onCreateView( 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 3e2a029c6..789b7c869 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 @@ -2,26 +2,104 @@ package com.geeksville.mesh.ui.preview import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.geeksville.mesh.DeviceMetrics +import com.geeksville.mesh.DeviceMetrics.Companion.currentTime +import com.geeksville.mesh.EnvironmentMetrics +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.MeshUser import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.Position +import kotlin.random.Random class NodeInfoPreviewParameterProvider: PreviewParameterProvider { + + val mickeyMouse = NodeInfo( + num = 1955, + position = Position( + latitude = 33.812511, + longitude = -117.918976, + altitude = 138, + ), + lastHeard = currentTime(), + channel = 0, + snr = 12.5F, + rssi = -42, + deviceMetrics = DeviceMetrics( + channelUtilization = 2.4F, + airUtilTx = 3.5F, + batteryLevel = 85, + voltage = 3.7F + ), + user = MeshUser( + longName = "Micky Mouse", + shortName = "MM", + id = "mickeyMouseId", + hwModel = MeshProtos.HardwareModel.TBEAM + ) + ) + + private val minnieMouse = mickeyMouse.copy( + num = Random.nextInt(), + user = MeshUser( + longName = "Minnie Mouse", + shortName = "MiMo", + id = "minnieMouseId", + hwModel = MeshProtos.HardwareModel.HELTEC_V3 + ), + snr = 12.5F, + rssi = -42, + position = null + ) + + private val donaldDuck = NodeInfo( + num = Random.nextInt(), + position = Position( + latitude = 33.80523471893125, + longitude = -117.92084605996297, + altitude = 121, + ), + lastHeard = currentTime() - 300, + channel = 0, + snr = 12.5F, + rssi = -42, + deviceMetrics = DeviceMetrics( + channelUtilization = 2.4F, + airUtilTx = 3.5F, + batteryLevel = 85, + voltage = 3.7F + ), + user = MeshUser( + longName = "Donald Duck, the Grand Duck of the Ducks", + shortName = "DoDu", + id = "donaldDuckId", + hwModel = MeshProtos.HardwareModel.HELTEC_V3, + ), + environmentMetrics = EnvironmentMetrics( + temperature = 28.0F, + relativeHumidity = 50.0F, + barometricPressure = 1013.25F, + gasResistance = 0.0F, + voltage = 3.7F, + current = 0.0F + ) + ) + + private val unknown = donaldDuck.copy( + user = null, + environmentMetrics = null + ) + + private val almostNothing = NodeInfo( + num = Random.nextInt(), + ) + override val values: Sequence get() = sequenceOf( - NodeInfo( - num = 1, - position = null, - lastHeard = 0, - channel = 0, - snr = 12.5F, - rssi = -42, - deviceMetrics = DeviceMetrics( - channelUtilization = 2.4F, - airUtilTx = 3.5F, - batteryLevel = 85, - voltage = 3.7F - ), - user = null - ) + mickeyMouse, // "this" node + unknown, + almostNothing, + minnieMouse, + donaldDuck ) + } \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index 0817f6ff3..96216953c 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -1,123 +1,18 @@ - + tools:composableName="com.geeksville.mesh.ui.NodeInfoKt.NodeInfoSimplePreview" + /> - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5cb7f18a3..bd825c2fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ 55.332244 34.442211 hey I found the cache, it is over here next to the big tiger. I\'m kinda scared. + \??? + Channel Name Channel options QR code