diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/CooldownIconButton.kt
similarity index 96%
rename from feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/CooldownIconButton.kt
index b6c27c3be..91348bc2c 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/CooldownIconButton.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.node.component
+package org.meshtastic.core.ui.component
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
@@ -36,8 +36,8 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.theme.AppTheme
-internal const val COOL_DOWN_TIME_MS = 30000L
-internal const val REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS = 180000L // 3 minutes
+const val COOL_DOWN_TIME_MS = 30000L
+const val REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS = 180000L // 3 minutes
@Composable
fun CooldownIconButton(
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/DeliveryInfoDialog.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DeliveryInfoDialog.kt
similarity index 94%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/DeliveryInfoDialog.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DeliveryInfoDialog.kt
index f02ac9218..3a53f4310 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/DeliveryInfoDialog.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/DeliveryInfoDialog.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging
+package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@@ -33,15 +33,13 @@ import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.close
import org.meshtastic.core.resources.relays
import org.meshtastic.core.resources.resend
-import org.meshtastic.core.ui.component.MeshtasticDialog
-@Suppress("UnusedParameter")
@Composable
fun DeliveryInfo(
title: StringResource,
resendOption: Boolean,
text: StringResource? = null,
- relayNodeName: String? = null,
+ @Suppress("UNUSED_PARAMETER") relayNodeName: String? = null,
relays: Int = 0,
onConfirm: (() -> Unit) = {},
onDismiss: () -> Unit = {},
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCard.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/InfoCard.kt
similarity index 96%
rename from feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCard.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/InfoCard.kt
index 927f37592..4175cdd2c 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCard.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/InfoCard.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.node.component
+package org.meshtastic.core.ui.component
import android.content.ClipData
import androidx.compose.foundation.ExperimentalFoundationApi
@@ -120,6 +120,6 @@ fun InfoCard(
}
@Composable
-internal fun DrawableInfoCard(iconRes: DrawableResource, text: String, value: String, rotateIcon: Float = 0f) {
+fun DrawableInfoCard(iconRes: DrawableResource, text: String, value: String, rotateIcon: Float = 0f) {
InfoCard(iconRes = iconRes, text = text, value = value, rotateIcon = rotateIcon)
}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActions.kt
similarity index 93%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActions.kt
index 3ef1e3ccb..6efe377bb 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActions.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActions.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging.component
+package org.meshtastic.core.ui.component
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
@@ -49,7 +49,7 @@ import org.meshtastic.core.resources.reply
import org.meshtastic.core.ui.emoji.EmojiPickerDialog
@Composable
-internal fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
+fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
var showEmojiPickerDialog by remember { mutableStateOf(false) }
if (showEmojiPickerDialog) {
EmojiPickerDialog(
@@ -66,7 +66,7 @@ internal fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
}
@Composable
-private fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
+fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
onClick = onClick,
content = {
Icon(imageVector = Icons.AutoMirrored.Filled.Reply, contentDescription = stringResource(Res.string.reply))
@@ -74,7 +74,7 @@ private fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
)
@Composable
-internal fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: MessageStatus, fromLocal: Boolean) =
+fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: MessageStatus, fromLocal: Boolean) =
AnimatedVisibility(visible = fromLocal) {
IconButton(onClick = onStatusClick) {
Crossfade(targetState = status, label = "MessageStatusIcon") { currentStatus ->
@@ -98,7 +98,7 @@ internal fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: Message
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
-internal fun MessageActions(
+fun MessageActions(
modifier: Modifier = Modifier,
isLocal: Boolean = false,
status: MessageStatus?,
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActionsBottomSheet.kt
similarity index 99%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActionsBottomSheet.kt
index f95c64b45..6eb30397c 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageActionsBottomSheet.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageActionsBottomSheet.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging.component
+package org.meshtastic.core.ui.component
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageBubble.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageBubble.kt
similarity index 95%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageBubble.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageBubble.kt
index 01466613b..2d0bf72e8 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageBubble.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageBubble.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging.component
+package org.meshtastic.core.ui.component
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp
* @param hasSamePrev Whether the previous message in the list is from the same sender.
* @param hasSameNext Whether the next message in the list is from the same sender.
*/
-internal fun getMessageBubbleShape(
+fun getMessageBubbleShape(
cornerRadius: Dp,
isSender: Boolean,
hasSamePrev: Boolean = false,
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageItem.kt
similarity index 98%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageItem.kt
index 115e3633e..c582573bb 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MessageItem.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging.component
+package org.meshtastic.core.ui.component
import android.content.ClipData
import androidx.compose.foundation.background
@@ -71,11 +71,6 @@ import org.meshtastic.core.resources.filter_message_label
import org.meshtastic.core.resources.message_delivery_status
import org.meshtastic.core.resources.reply
import org.meshtastic.core.resources.sample_message
-import org.meshtastic.core.ui.component.AutoLinkText
-import org.meshtastic.core.ui.component.NodeChip
-import org.meshtastic.core.ui.component.Rssi
-import org.meshtastic.core.ui.component.Snr
-import org.meshtastic.core.ui.component.TransportIcon
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.emoji.EmojiPicker
import org.meshtastic.core.ui.icon.Acknowledged
@@ -92,7 +87,7 @@ import org.meshtastic.core.ui.theme.MessageItemColors
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
-internal fun MessageItem(
+fun MessageItem(
modifier: Modifier = Modifier,
node: Node,
ourNode: Node,
@@ -449,7 +444,7 @@ private fun OriginalMessageSnippet(
@PreviewLightDark
@Composable
-private fun MessageItemPreview() {
+fun MessageItemPreview() {
val sent =
Message(
text = stringResource(Res.string.sample_message),
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeItem.kt
similarity index 98%
rename from feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeItem.kt
index f8b895552..1021cd6b8 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeItem.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeItem.kt
@@ -16,7 +16,7 @@
*/
@file:Suppress("MagicNumber")
-package org.meshtastic.feature.node.component
+package org.meshtastic.core.ui.component
import android.content.res.Configuration
import androidx.compose.foundation.combinedClickable
@@ -64,6 +64,7 @@ import org.meshtastic.core.resources.elevation_suffix
import org.meshtastic.core.resources.signal_quality
import org.meshtastic.core.resources.unknown_username
import org.meshtastic.core.resources.voltage
+import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.ui.component.AirQualityInfo
import org.meshtastic.core.ui.component.ChannelInfo
import org.meshtastic.core.ui.component.DistanceInfo
@@ -117,7 +118,7 @@ fun NodeItem(
val isFavorite = remember(thatNode) { thatNode.isFavorite }
val isMuted = remember(thatNode) { thatNode.isMuted }
val isIgnored = thatNode.isIgnored
- val originalLongName = (thatNode.user.long_name ?: "").ifEmpty { stringResource(Res.string.unknown_username) }
+ val originalLongName = thatNode.user.long_name.ifEmpty { stringResource(Res.string.unknown_username) }
val isThisNode = remember(thatNode) { thisNode?.num == thatNode.num }
val system =
@@ -318,7 +319,7 @@ private fun gatherSensors(node: Node, tempInFahrenheit: Boolean, contentColor: C
val env = node.environmentMetrics
val pax = node.paxcounter
- if ((pax.ble ?: 0) != 0 || (pax.wifi ?: 0) != 0) {
+ if (pax.ble != 0 || pax.wifi != 0) {
items.add { PaxcountInfo(pax = "B:${pax.ble ?: 0} W:${pax.wifi ?: 0}", contentColor = contentColor) }
}
if ((env.temperature ?: 0f) != 0f) {
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeStatusIcons.kt
similarity index 98%
rename from feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeStatusIcons.kt
index 5546b3cbe..d86bdc4c8 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/NodeStatusIcons.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.node.component
+package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
@@ -182,7 +182,7 @@ private fun StatusBadge(
tint: Color = LocalContentColor.current,
) {
TooltipBox(
- positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
+ positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text(stringResource(tooltipText)) } },
state = rememberTooltipState(),
) {
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/Reaction.kt
similarity index 97%
rename from feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
rename to core/ui/src/main/kotlin/org/meshtastic/core/ui/component/Reaction.kt
index 0011e1e5c..cb91d83da 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/component/Reaction.kt
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/Reaction.kt
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.meshtastic.feature.messaging.component
+package org.meshtastic.core.ui.component
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
@@ -72,14 +72,10 @@ import org.meshtastic.core.resources.message_status_enroute
import org.meshtastic.core.resources.message_status_queued
import org.meshtastic.core.resources.react
import org.meshtastic.core.resources.you
-import org.meshtastic.core.ui.component.BottomSheetDialog
-import org.meshtastic.core.ui.component.Rssi
-import org.meshtastic.core.ui.component.Snr
import org.meshtastic.core.ui.emoji.EmojiPickerDialog
import org.meshtastic.core.ui.icon.Hops
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme
-import org.meshtastic.feature.messaging.DeliveryInfo
import org.meshtastic.proto.User
@Composable
@@ -137,7 +133,7 @@ private fun ReactionItem(
@OptIn(ExperimentalLayoutApi::class)
@Composable
-internal fun ReactionRow(
+fun ReactionRow(
modifier: Modifier = Modifier,
reactions: List = emptyList(),
myId: String? = null,
@@ -193,7 +189,7 @@ private fun AddReactionButton(modifier: Modifier = Modifier, onSendReaction: (St
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
-internal fun ReactionDialog(
+fun ReactionDialog(
reactions: List,
onDismiss: () -> Unit = {},
myId: String? = null,
@@ -334,7 +330,7 @@ private fun ReactionItemPreview() {
@Preview
@Composable
-private fun ReactionRowPreview() {
+fun ReactionRowPreview() {
AppTheme {
ReactionRow(
reactions =
diff --git a/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/AppScreenshotTest.kt b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/AppScreenshotTest.kt
new file mode 100644
index 000000000..52a6952d5
--- /dev/null
+++ b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/AppScreenshotTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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 .
+ */
+package org.meshtastic.core.ui
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.tools.screenshot.PreviewTest
+import org.meshtastic.core.model.Channel
+import org.meshtastic.core.ui.component.AdaptiveTwoPane
+import org.meshtastic.core.ui.component.MainAppBar
+import org.meshtastic.core.ui.component.preview.previewNode
+import org.meshtastic.core.ui.theme.AppTheme
+import org.meshtastic.core.ui.qr.ScannedQrCodeDialog
+import org.meshtastic.proto.ChannelSet
+
+class AppScreenshotTest {
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun MainAppBarTest() {
+ AppTheme {
+ MainAppBar(
+ title = "Meshtastic",
+ subtitle = "Connected to Node",
+ ourNode = previewNode,
+ showNodeChip = true,
+ canNavigateUp = false,
+ onNavigateUp = {},
+ actions = {},
+ onClickChip = {}
+ )
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun ScannedQrCodeDialogTest() {
+ AppTheme {
+ ScannedQrCodeDialog(
+ channels = ChannelSet(
+ settings = listOf(Channel.default.settings),
+ lora_config = Channel.default.loraConfig
+ ),
+ incoming = ChannelSet(
+ settings = listOf(Channel.default.settings),
+ lora_config = Channel.default.loraConfig
+ ),
+ onDismiss = {},
+ onConfirm = {},
+ )
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true, widthDp = 800)
+ @Composable
+ fun AdaptiveTwoPaneExpandedTest() {
+ AppTheme {
+ AdaptiveTwoPane(
+ first = { Text("Left Pane") },
+ second = { Text("Right Pane") }
+ )
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true, widthDp = 400)
+ @Composable
+ fun AdaptiveTwoPaneCompactTest() {
+ AppTheme {
+ AdaptiveTwoPane(
+ first = { Text("Top Pane") },
+ second = { Text("Bottom Pane") }
+ )
+ }
+ }
+}
diff --git a/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/ComponentScreenshotTest.kt b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/ComponentScreenshotTest.kt
index 9da44d81e..f02ecd106 100644
--- a/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/ComponentScreenshotTest.kt
+++ b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/ComponentScreenshotTest.kt
@@ -12,11 +12,14 @@
* 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 .
+ * along with this program. Of not, see .
*/
package org.meshtastic.core.ui
+import android.graphics.Bitmap
+import android.net.Uri
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
@@ -29,8 +32,11 @@ 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.model.Channel
+import org.meshtastic.core.ui.component.AutoLinkText
import org.meshtastic.core.ui.component.BatteryInfoPreviewParameterProvider
import org.meshtastic.core.ui.component.ChannelInfo
+import org.meshtastic.core.ui.component.ChannelSelection
import org.meshtastic.core.ui.component.DistanceInfo
import org.meshtastic.core.ui.component.ElevationInfo
import org.meshtastic.core.ui.component.HopsInfo
@@ -40,13 +46,18 @@ 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.QrDialog
import org.meshtastic.core.ui.component.SatelliteCountInfo
+import org.meshtastic.core.ui.component.SecurityIcon
+import org.meshtastic.core.ui.component.SecurityState
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.TransportIcon
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.Config
+import org.meshtastic.proto.MeshPacket
class ComponentScreenshotTest {
@@ -170,4 +181,69 @@ class ComponentScreenshotTest {
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Pill)
}
}
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun AutoLinkTextTest() {
+ AppTheme {
+ AutoLinkText("Check out https://meshtastic.org for more info!")
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun SecurityIconTest() {
+ AppTheme {
+ Column {
+ SecurityState.entries.forEach { state ->
+ SecurityIcon(securityState = state)
+ }
+ }
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun TransportIconTest() {
+ AppTheme {
+ Column {
+ TransportIcon(transport = MeshPacket.TransportMechanism.TRANSPORT_INTERNAL.value, viaMqtt = false)
+ TransportIcon(transport = MeshPacket.TransportMechanism.TRANSPORT_MQTT.value, viaMqtt = true)
+ TransportIcon(transport = MeshPacket.TransportMechanism.TRANSPORT_MULTICAST_UDP.value, viaMqtt = false)
+ }
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun ChannelSelectionTest() {
+ AppTheme {
+ ChannelSelection(
+ index = 0,
+ title = "LongFast",
+ enabled = true,
+ isSelected = true,
+ onSelected = {},
+ channel = Channel.default
+ )
+ }
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun QrDialogTest() {
+ AppTheme {
+ QrDialog(
+ title = "Share Contact",
+ uri = Uri.parse("https://meshtastic.org/u/dummy"),
+ qrCode = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+ onDismiss = {}
+ )
+ }
+ }
}
diff --git a/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/MessageItemScreenshotTest.kt b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/MessageItemScreenshotTest.kt
new file mode 100644
index 000000000..fe54b728a
--- /dev/null
+++ b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/MessageItemScreenshotTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 .
+ */
+package org.meshtastic.core.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.tools.screenshot.PreviewTest
+import org.meshtastic.core.ui.component.MessageItemPreview
+import org.meshtastic.core.ui.component.ReactionRowPreview
+
+class MessageItemScreenshotTest {
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun MessageItemTest() {
+ MessageItemPreview()
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun ReactionRowTest() {
+ ReactionRowPreview()
+ }
+}
diff --git a/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/NodeItemScreenshotTest.kt b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/NodeItemScreenshotTest.kt
new file mode 100644
index 000000000..b25f500f2
--- /dev/null
+++ b/core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/NodeItemScreenshotTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 .
+ */
+package org.meshtastic.core.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import com.android.tools.screenshot.PreviewTest
+import org.meshtastic.core.database.model.Node
+import org.meshtastic.core.ui.component.NodeInfoPreview
+import org.meshtastic.core.ui.component.NodeInfoSignalPreview
+import org.meshtastic.core.ui.component.NodeInfoSimplePreview
+import org.meshtastic.core.ui.component.NodeInfoStatusPreview
+import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
+
+class NodeItemScreenshotTest {
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun NodeInfoSimpleTest() {
+ NodeInfoSimplePreview()
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun NodeInfoStatusTest() {
+ NodeInfoStatusPreview()
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun NodeInfoSignalTest() {
+ NodeInfoSignalPreview()
+ }
+
+ @PreviewTest
+ @Preview(showBackground = true)
+ @Composable
+ fun NodeInfoTest(@PreviewParameter(NodePreviewParameterProvider::class) node: Node) {
+ NodeInfoPreview(thatNode = node)
+ }
+}
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneCompactTest_e7711029_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneCompactTest_e7711029_0.png
new file mode 100644
index 000000000..e697fecaa
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneCompactTest_e7711029_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneExpandedTest_37699c78_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneExpandedTest_37699c78_0.png
new file mode 100644
index 000000000..7cb896446
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneExpandedTest_37699c78_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/MainAppBarTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/MainAppBarTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/MainAppBarTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/ScannedQrCodeDialogTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/ScannedQrCodeDialogTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/ScannedQrCodeDialogTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/AutoLinkTextTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/AutoLinkTextTest_748aa731_0.png
new file mode 100644
index 000000000..b26fff5b0
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/AutoLinkTextTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/ChannelSelectionTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/ChannelSelectionTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/ChannelSelectionTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_1.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_1.png
index 10a143fca..3b2254435 100644
Binary files a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_1.png and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_1.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_2.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_2.png
index 6244babaf..de1ff97dc 100644
Binary files a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_2.png and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_2.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_3.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_3.png
index 41ce867ae..7454e8756 100644
Binary files a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_3.png and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_3.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_4.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_4.png
index d032a70d5..9a094dedf 100644
Binary files a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_4.png and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/NodeChipTest_748aa731_41783942_4.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/QrDialogTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/QrDialogTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/QrDialogTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/SecurityIconTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/SecurityIconTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/SecurityIconTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/TransportIconTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/TransportIconTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/TransportIconTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/MessageItemTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/MessageItemTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/MessageItemTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/ReactionRowTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/ReactionRowTest_748aa731_0.png
new file mode 100644
index 000000000..a6dce867e
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/ReactionRowTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSignalTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSignalTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSignalTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSimpleTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSimpleTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSimpleTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoStatusTest_748aa731_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoStatusTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoStatusTest_748aa731_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_0.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_0.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_1.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_1.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_1.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_2.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_2.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_2.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_3.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_3.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_3.png differ
diff --git a/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_4.png b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_4.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_4.png differ
diff --git a/feature/messaging/build.gradle.kts b/feature/messaging/build.gradle.kts
index 97b81c776..90a9d6fa1 100644
--- a/feature/messaging/build.gradle.kts
+++ b/feature/messaging/build.gradle.kts
@@ -20,9 +20,15 @@ plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)
+ alias(libs.plugins.screenshot)
}
-configure { namespace = "org.meshtastic.feature.messaging" }
+configure {
+ namespace = "org.meshtastic.feature.messaging"
+ experimentalProperties["android.experimental.enableScreenshotTest"] = true
+
+ testOptions { unitTests { isIncludeAndroidResources = true } }
+}
dependencies {
implementation(projects.core.analytics)
@@ -68,4 +74,10 @@ dependencies {
testImplementation(libs.androidx.work.testing)
testImplementation(libs.androidx.test.core)
testImplementation(libs.robolectric)
+
+ screenshotTestImplementation(libs.screenshot.validation.api)
+ screenshotTestImplementation(libs.androidx.compose.ui.tooling)
+ screenshotTestImplementation(libs.compose.multiplatform.runtime)
+ screenshotTestImplementation(libs.compose.multiplatform.resources)
+ screenshotTestImplementation(projects.core.resources)
}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageListPaged.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageListPaged.kt
index 25be10430..772e88c77 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageListPaged.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageListPaged.kt
@@ -68,8 +68,9 @@ import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.new_messages_below
-import org.meshtastic.feature.messaging.component.MessageItem
-import org.meshtastic.feature.messaging.component.ReactionDialog
+import org.meshtastic.core.ui.component.DeliveryInfo
+import org.meshtastic.core.ui.component.MessageItem
+import org.meshtastic.core.ui.component.ReactionDialog
internal data class MessageListHandlers(
val onUnreadChanged: (Long, Long) -> Unit,
diff --git a/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/MessageItemTest_748aa731_0.png b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/MessageItemTest_748aa731_0.png
new file mode 100644
index 000000000..bd0192643
Binary files /dev/null and b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/MessageItemTest_748aa731_0.png differ
diff --git a/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/RawStringTest_748aa731_0.png b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/RawStringTest_748aa731_0.png
new file mode 100644
index 000000000..0f3d0eab5
Binary files /dev/null and b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/RawStringTest_748aa731_0.png differ
diff --git a/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/SimpleBoxTest_748aa731_0.png b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/SimpleBoxTest_748aa731_0.png
new file mode 100644
index 000000000..7dd2ab5b5
Binary files /dev/null and b/feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/SimpleBoxTest_748aa731_0.png differ
diff --git a/feature/node/build.gradle.kts b/feature/node/build.gradle.kts
index de857e9d9..b2ed1f1f9 100644
--- a/feature/node/build.gradle.kts
+++ b/feature/node/build.gradle.kts
@@ -21,6 +21,7 @@ plugins {
alias(libs.plugins.meshtastic.android.library.flavors)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)
+ alias(libs.plugins.screenshot)
}
configure {
@@ -29,6 +30,8 @@ configure {
defaultConfig { manifestPlaceholders["MAPS_API_KEY"] = "DEBUG_KEY" }
testOptions { unitTests { isIncludeAndroidResources = true } }
+
+ experimentalProperties["android.experimental.enableScreenshotTest"] = true
}
dependencies {
@@ -70,4 +73,10 @@ dependencies {
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.robolectric)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+
+ screenshotTestImplementation(libs.screenshot.validation.api)
+ screenshotTestImplementation(libs.androidx.compose.ui.tooling)
+ screenshotTestImplementation(libs.compose.multiplatform.runtime)
+ screenshotTestImplementation(libs.compose.multiplatform.resources)
+ screenshotTestImplementation(projects.core.resources)
}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
index 05cfd5fc5..d581c8a14 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/EnvironmentMetrics.kt
@@ -61,6 +61,8 @@ import org.meshtastic.core.resources.uv_lux
import org.meshtastic.core.resources.voltage
import org.meshtastic.core.resources.weight
import org.meshtastic.core.resources.wind
+import org.meshtastic.core.ui.component.DrawableInfoCard
+import org.meshtastic.core.ui.component.InfoCard
import org.meshtastic.feature.node.model.DrawableMetricInfo
import org.meshtastic.feature.node.model.VectorMetricInfo
import org.meshtastic.proto.Config
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCardPreview.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCardPreview.kt
deleted file mode 100644
index f7d46a939..000000000
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCardPreview.kt
+++ /dev/null
@@ -1,93 +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 .
- */
-
-package org.meshtastic.feature.node.component
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Navigation
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.tooling.preview.Preview
-
-@Preview(name = "Wind Dir -359°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirectionn359() {
- PreviewWindDirectionItem(-359f)
-}
-
-@Preview(name = "Wind Dir 0°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection0() {
- PreviewWindDirectionItem(0f)
-}
-
-@Preview(name = "Wind Dir 45°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection45() {
- PreviewWindDirectionItem(45f)
-}
-
-@Preview(name = "Wind Dir 90°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection90() {
- PreviewWindDirectionItem(90f)
-}
-
-@Preview(name = "Wind Dir 180°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection180() {
- PreviewWindDirectionItem(180f)
-}
-
-@Preview(name = "Wind Dir 225°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection225() {
- PreviewWindDirectionItem(225f)
-}
-
-@Preview(name = "Wind Dir 270°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection270() {
- PreviewWindDirectionItem(270f)
-}
-
-@Preview(name = "Wind Dir 315°")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirection315() {
- PreviewWindDirectionItem(315f)
-}
-
-@Preview(name = "Wind Dir -45")
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirectionN45() {
- PreviewWindDirectionItem(-45f)
-}
-
-@Suppress("detekt:MagicNumber")
-@Composable
-private fun PreviewWindDirectionItem(windDirection: Float, windSpeed: String = "5 m/s") {
- val normalizedBearing = (windDirection + 180) % 360
- InfoCard(icon = Icons.Outlined.Navigation, text = "Wind", value = windSpeed, rotateIcon = normalizedBearing)
-}
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
index 6927a7861..a60c27821 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/PowerMetrics.kt
@@ -31,6 +31,7 @@ import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.channel_1
import org.meshtastic.core.resources.channel_2
import org.meshtastic.core.resources.channel_3
+import org.meshtastic.core.ui.component.InfoCard
import org.meshtastic.feature.node.model.VectorMetricInfo
/**
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
index 0cee70ea8..61e551467 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/TelemetricActionsSection.kt
@@ -55,6 +55,9 @@ import org.meshtastic.core.resources.request_air_quality_metrics
import org.meshtastic.core.resources.request_telemetry
import org.meshtastic.core.resources.telemetry
import org.meshtastic.core.resources.userinfo
+import org.meshtastic.core.ui.component.COOL_DOWN_TIME_MS
+import org.meshtastic.core.ui.component.CooldownOutlinedIconButton
+import org.meshtastic.core.ui.component.REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS
import org.meshtastic.core.ui.icon.AirQuality
import org.meshtastic.core.ui.icon.LineAxis
import org.meshtastic.core.ui.icon.MeshtasticIcons
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
index f2a823296..77fe78a9a 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
@@ -88,7 +88,7 @@ import org.meshtastic.core.ui.qr.ScannedQrCodeDialog
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.node.component.NodeFilterTextField
-import org.meshtastic.feature.node.component.NodeItem
+import org.meshtastic.core.ui.component.NodeItem
import org.meshtastic.proto.SharedContact
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/NeighborInfoLog.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/NeighborInfoLog.kt
index 006e02fcf..37f5437c2 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/NeighborInfoLog.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/NeighborInfoLog.kt
@@ -55,7 +55,7 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
import org.meshtastic.core.ui.util.annotateNeighborInfo
-import org.meshtastic.feature.node.component.CooldownIconButton
+import org.meshtastic.core.ui.component.CooldownIconButton
import org.meshtastic.feature.node.detail.NodeRequestEffect
@OptIn(ExperimentalFoundationApi::class)
diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
index 1fdd5cf5b..fab5f95ed 100644
--- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
+++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt
@@ -73,7 +73,7 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
import org.meshtastic.core.ui.util.annotateTraceroute
import org.meshtastic.feature.map.model.TracerouteOverlay
-import org.meshtastic.feature.node.component.CooldownIconButton
+import org.meshtastic.core.ui.component.CooldownIconButton
import org.meshtastic.feature.node.detail.NodeRequestEffect
import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
import org.meshtastic.proto.RouteDiscovery