Improved battery/voltage info in node list (#874)

* Move battery info to compose - always show voltage level and icons to match battery percentage
Use tool text in preview, rather than actually set text value
Simplify node info layout to avoid defining margins on everything

* Use compose preview in layout editor

* Add simple preview for use in layout
This commit is contained in:
Davis 2024-02-26 15:19:32 -07:00 committed by GitHub
parent 7fb2761dc6
commit 89438f3553
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 229 additions and 66 deletions

View file

@ -0,0 +1,83 @@
package com.geeksville.mesh.ui
import android.content.res.Configuration
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.theme.AppTheme
@Composable
fun BatteryInfo(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"
in 5 .. 14 -> R.drawable.ic_battery_outline to infoString
in 15..34 -> R.drawable.ic_battery_low to infoString
in 35..79 -> R.drawable.ic_battery_medium to infoString
in 80..100 -> R.drawable.ic_battery_high to infoString
101 -> R.drawable.ic_power_plug_24 to "%.1fV".format(voltage)
else -> R.drawable.ic_battery_unknown to (voltage?.let { "%.1fV".format(it) } ?: "")
}
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.height(18.dp),
imageVector = ImageVector.vectorResource(id = image),
contentDescription = null,
tint = MaterialTheme.colors.onSurface,
)
Text(
text = level,
color = MaterialTheme.colors.onSurface,
fontSize = MaterialTheme.typography.button.fontSize
)
}
}
@Composable
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
fun BatteryInfoPreview(
@PreviewParameter(BatteryInfoPreviewParameterProvider::class)
batteryInfo: Pair<Int?, Float?>
) {
AppTheme {
BatteryInfo(batteryInfo.first, batteryInfo.second)
}
}
@Composable
@Preview
fun BatteryInfoPreviewSimple() {
AppTheme {
BatteryInfo(85, 3.7F)
}
}
class BatteryInfoPreviewParameterProvider : PreviewParameterProvider<Pair<Int?, Float?>> {
override val values: Sequence<Pair<Int?, Float?>>
get() = sequenceOf(
85 to 3.7F,
2 to 3.7F,
12 to 3.7F,
28 to 3.7F,
50 to 3.7F,
101 to 4.9F,
null to 4.5F,
null to null
)
}

View file

@ -28,6 +28,7 @@ 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
import com.geeksville.mesh.util.formatAgo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@ -58,12 +59,11 @@ class UsersFragment : ScreenFragment("Users"), Logging {
val nodeNameView = itemView.nodeNameView
val distanceView = itemView.distanceView
val coordsView = itemView.coordsView
val batteryPctView = itemView.batteryPercentageView
val lastTime = itemView.lastConnectionView
val powerIcon = itemView.batteryIcon
val signalView = itemView.signalView
val envMetrics = itemView.envMetrics
val background = itemView.nodeCard
val batteryInfo = itemView.batteryInfo
fun blink() {
val bg = background.backgroundTintList
@ -85,6 +85,14 @@ class UsersFragment : ScreenFragment("Users"), Logging {
}
}
}
fun bind(batteryLevel: Int?, voltage: Float?) {
batteryInfo.setContent {
AppTheme {
BatteryInfo(batteryLevel, voltage)
}
}
}
}
private val nodesAdapter = object : RecyclerView.Adapter<ViewHolder>() {
@ -232,6 +240,9 @@ class UsersFragment : ScreenFragment("Users"), Logging {
val user = n.user
val (textColor, nodeColor) = n.colors
val isIgnored: Boolean = ignoreIncomingList.contains(n.num)
holder.bind(n.batteryLevel, n.voltage)
with(holder.chipNode) {
text = (user?.shortName ?: "UNK").strikeIf(isIgnored)
chipBackgroundColor = ColorStateList.valueOf(nodeColor)
@ -260,7 +271,6 @@ class UsersFragment : ScreenFragment("Users"), Logging {
} else {
holder.distanceView.visibility = View.INVISIBLE
}
renderBattery(n.batteryLevel, n.voltage, holder)
holder.lastTime.text = formatAgo(n.lastHeard)
@ -312,24 +322,6 @@ class UsersFragment : ScreenFragment("Users"), Logging {
}
}
private fun renderBattery(
battery: Int?,
voltage: Float?,
holder: ViewHolder
) {
val (image, text) = when (battery) {
in 0..100 -> R.drawable.ic_battery_full_24 to "%d%% %.2fV".format(battery, voltage)
101 -> R.drawable.ic_power_plug_24 to ""
else -> R.drawable.ic_battery_full_24 to "?"
}
holder.batteryPctView.text = text
holder.powerIcon.setImageDrawable(context?.let {
ContextCompat.getDrawable(it, image)
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?

View file

@ -15,4 +15,6 @@ val LightPink = Color(0xFFFFE6E6)
val LightGreen = Color(0xFFCFE8A9)
val LightRed = Color(0xFFFFB3B3)
val MeshtasticGreen = Color(0xFF67EA94)
val MeshtasticGreen = Color(0xFF67EA94)
val AlmostWhite = Color(0xB3FFFFFF)
val AlmostBlack = Color(0x8A000000)

View file

@ -7,15 +7,17 @@ import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
primary = Purple200,
primary = MeshtasticGreen,
primaryVariant = Purple700,
secondary = Teal200
secondary = Teal200,
onSurface = AlmostWhite
)
private val LightColorPalette = lightColors(
primary = SkyBlue,
primary = MeshtasticGreen,
primaryVariant = LightSkyBlue,
secondary = Teal200
secondary = Teal200,
onSurface = AlmostBlack
/* Other default colors to override
background = Color.White,

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M14,20H6V6H14M14.67,4H13V2H7V4H5.33C4.6,4 4,4.6 4,5.33V20.67C4,21.4 4.6,22 5.33,22H14.67C15.4,22 16,21.4 16,20.67V5.33C16,4.6 15.4,4 14.67,4M21,7H19V13H21V8M21,15H19V17H21V15Z"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33C6.6,4 6,4.6 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67C17.41,22 18,21.41 18,20.67V5.33C18,4.6 17.4,4 16.67,4M15,16H9V19H15V16M15,7H9V10H15V7M15,11.5H9V14.5H15V11.5Z"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33C6.6,4 6,4.6 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67C17.41,22 18,21.41 18,20.67V5.33C18,4.6 17.4,4 16.67,4M15,16H9V19H15V16"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33C6.6,4 6,4.6 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67C17.41,22 18,21.41 18,20.67V5.33C18,4.6 17.4,4 16.67,4M15,16H9V19H15V16M15,11.5H9V14.5H15V11.5Z"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0,0 0,6 5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0,0 0,18 20.67V5.33C18,4.6 17.4,4 16.67,4Z"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -0,0 +1,14 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="@android:color/white"
android:pathData="M15.07,12.25L14.17,13.17C13.63,13.71 13.25,14.18 13.09,15H11.05C11.16,14.1 11.56,13.28 12.17,12.67L13.41,11.41C13.78,11.05 14,10.55 14,10C14,8.89 13.1,8 12,8A2,2 0,0 0,10 10H8A4,4 0,0 1,12 6A4,4 0,0 1,16 10C16,10.88 15.64,11.68 15.07,12.25M13,19H11V17H13M16.67,4H15V2H9V4H7.33A1.33,1.33 0,0 0,6 5.33V20.66C6,21.4 6.6,22 7.33,22H16.67C17.4,22 18,21.4 18,20.66V5.33C18,4.59 17.4,4 16.67,4Z"
android:fillAlpha="0.5"
/>
</vector>

View file

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false">
android:clipToPadding="false"
>
<com.google.android.material.card.MaterialCardView
style="@style/Widget.App.CardView"
@ -15,110 +18,107 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="8dp"
>
<com.google.android.material.chip.Chip
android:id="@+id/chip_node"
android:layout_width="72dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/some_username"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/some_username"
/>
<TextView
android:id="@+id/nodeNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/unknown_username"
app:layout_constraintStart_toEndOf="@+id/chip_node"
app:layout_constraintTop_toTopOf="@+id/chip_node" />
app:layout_constraintTop_toTopOf="@+id/chip_node"
tools:text="@string/unknown_username"
/>
<TextView
android:id="@+id/distance_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/sample_distance"
app:layout_constraintTop_toBottomOf="@+id/chip_node"
app:layout_constraintEnd_toEndOf="@+id/chip_node"
app:layout_constraintStart_toStartOf="@+id/chip_node"
app:layout_constraintTop_toBottomOf="@+id/chip_node" />
android:layout_marginTop="8dp"
tools:text="@string/sample_distance"
/>
<TextView
android:id="@+id/coords_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/sample_coords"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toEndOf="@+id/chip_node"
app:layout_constraintTop_toBottomOf="@+id/nodeNameView"
app:layout_constraintVertical_bias="0.0" />
app:layout_constraintVertical_bias="0.0"
tools:text="@string/sample_coords"
/>
<ImageView
android:id="@+id/batteryIcon"
<androidx.compose.ui.platform.ComposeView
android:id="@+id/batteryInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/batteryPercentageView"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_battery_full_24" />
<TextView
android:id="@+id/batteryPercentageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="100%"
app:layout_constraintBottom_toBottomOf="@+id/nodeNameView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/nodeNameView" />
tools:composableName="com.geeksville.mesh.ui.BatteryInfoKt.BatteryInfoPreviewSimple"
/>
<ImageView
android:id="@+id/lastCommIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginEnd="2dp"
android:layout_marginBottom="8dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/signalView"
app:layout_constraintEnd_toStartOf="@+id/lastConnectionView"
app:layout_constraintTop_toBottomOf="@id/batteryIcon"
app:layout_constraintTop_toBottomOf="@id/batteryInfo"
app:srcCompat="@drawable/ic_antenna_24" />
<TextView
android:id="@+id/lastConnectionView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="11h01 PM"
android:visibility="visible"
app:layout_constraintTop_toTopOf="@+id/lastCommIcon"
app:layout_constraintBottom_toBottomOf="@+id/lastCommIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/lastCommIcon" />
tools:text="11h01 PM"
/>
<TextView
android:id="@+id/signalView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="rssi:-40 snr:-8"
app:layout_constraintTop_toBottomOf="@id/lastConnectionView"
app:layout_constraintBottom_toTopOf="@id/envMetrics"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/lastConnectionView" />
tools:text="RSSI: -40 SNR: -8"
/>
<TextView
android:id="@+id/envMetrics"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginTop="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>