refactor: Move node components to core:ui and add screenshot tests

Move various informational UI components from the node feature module to the core UI module to improve reusability across the application. Add a comprehensive screenshot testing suite for core components.

- Relocate ChannelInfo, DistanceInfo, ElevationInfo, HopsInfo, IconInfo, LastHeardInfo, SatelliteCountInfo, and TelemetryInfo to `org.meshtastic.core.ui.component`.
- Add `ComponentScreenshotTest` in `core:ui` along with baseline reference images for battery, signal, node chips, and other UI elements.
- Remove unused `ClickableTextField` from `core:ui`.
- Update `CompassBottomSheet` to use the relocated components.
This commit is contained in:
James Rich 2026-03-02 09:50:52 -06:00
parent eacabf4bd4
commit a19c463b37
46 changed files with 174 additions and 636 deletions

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
@Composable
fun ClickableTextField(
label: StringResource,
enabled: Boolean,
trailingIcon: ImageVector,
value: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
isError: Boolean = false,
) {
val source = remember { MutableInteractionSource() }
val isPressed by source.collectIsPressedAsState()
if (isPressed) onClick()
OutlinedTextField(
value,
onValueChange = {},
enabled = enabled,
readOnly = true,
label = { Text(stringResource(label)) },
trailingIcon = { Icon(trailingIcon, null) },
isError = isError,
interactionSource = source,
modifier = modifier,
)
}

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Android
import androidx.compose.material3.Surface
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 androidx.compose.ui.unit.dp
import com.android.tools.screenshot.PreviewTest
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.ui.component.BatteryInfoPreviewParameterProvider
import org.meshtastic.core.ui.component.ChannelInfo
import org.meshtastic.core.ui.component.DistanceInfo
import org.meshtastic.core.ui.component.ElevationInfo
import org.meshtastic.core.ui.component.HopsInfo
import org.meshtastic.core.ui.component.IAQScale
import org.meshtastic.core.ui.component.IaqDisplayMode
import org.meshtastic.core.ui.component.IndoorAirQuality
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.MaterialBatteryInfo
import org.meshtastic.core.ui.component.NodeChip
import org.meshtastic.core.ui.component.SatelliteCountInfo
import org.meshtastic.core.ui.component.SignalInfo
import org.meshtastic.core.ui.component.SwitchListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.Config
class ComponentScreenshotTest {
@PreviewTest
@Preview(showBackground = true)
@Composable
fun BatteryInfoTest(@PreviewParameter(BatteryInfoPreviewParameterProvider::class) info: Pair<Int?, Float?>) {
AppTheme {
MaterialBatteryInfo(level = info.first, voltage = info.second)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun SignalInfoTest(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
AppTheme {
SignalInfo(node = node)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun NodeChipTest(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
AppTheme {
NodeChip(node = node)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun DistanceInfoTest() {
AppTheme {
DistanceInfo(distance = "12.3 km")
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun ElevationInfoTest() {
AppTheme {
ElevationInfo(altitude = 1234, system = Config.DisplayConfig.DisplayUnits.METRIC, suffix = "m")
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun HopsInfoTest() {
AppTheme {
HopsInfo(hops = 3)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun SatelliteCountInfoTest() {
AppTheme {
SatelliteCountInfo(satCount = 8)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun ChannelInfoTest() {
AppTheme {
ChannelInfo(channel = 1)
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun ListItemTest() {
AppTheme {
ListItem(text = "Example Item", leadingIcon = Icons.Rounded.Android) {}
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun SwitchListItemTest() {
AppTheme {
SwitchListItem(checked = true, text = "Example Switch", onClick = {})
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun TitledCardTest() {
AppTheme {
Surface {
TitledCard(title = "Example Title") {
Box(modifier = Modifier.fillMaxWidth().height(50.dp))
}
}
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun IAQScaleTest() {
AppTheme {
IAQScale()
}
}
@PreviewTest
@Preview(showBackground = true)
@Composable
fun IndoorAirQualityPillTest() {
AppTheme {
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Pill)
}
}
}