From 85847e1144ccd4ac905a79450c9953c6eba6157f Mon Sep 17 00:00:00 2001 From: James Rich Date: Mon, 2 Mar 2026 10:32:55 -0600 Subject: [PATCH] refactor: Move common UI components to core:ui and add screenshot tests Relocate messaging and node-related UI components from feature modules to the shared core:ui module to improve reusability across the project. Enable the screenshot testing plugin and add comprehensive baseline tests for shared components. - **feature/messaging**: Move `MessageItem`, `MessageActions`, `MessageBubble`, and `Reaction` components to core:ui. - **feature/node**: Move `NodeItem`, `NodeStatusIcons`, `InfoCard`, and `CooldownIconButton` to core:ui. - **core/ui**: Add new screenshot tests for `MainAppBar`, `NodeItem`, `MessageItem`, and various common UI components. - **build.gradle.kts**: Configure the screenshot testing plugin and dependencies for node and messaging feature modules. - Update imports and references across the codebase to reflect relocated components. --- .../core/ui/component/CooldownIconButton.kt | 6 +- .../core/ui/component}/DeliveryInfoDialog.kt | 6 +- .../meshtastic/core/ui}/component/InfoCard.kt | 4 +- .../core/ui}/component/MessageActions.kt | 10 +- .../component/MessageActionsBottomSheet.kt | 2 +- .../core/ui}/component/MessageBubble.kt | 4 +- .../core/ui}/component/MessageItem.kt | 11 +- .../meshtastic/core/ui}/component/NodeItem.kt | 7 +- .../core/ui}/component/NodeStatusIcons.kt | 4 +- .../meshtastic/core/ui}/component/Reaction.kt | 12 +-- .../meshtastic/core/ui/AppScreenshotTest.kt | 94 ++++++++++++++++++ .../core/ui/ComponentScreenshotTest.kt | 78 ++++++++++++++- .../core/ui/MessageItemScreenshotTest.kt | 40 ++++++++ .../core/ui/NodeItemScreenshotTest.kt | 59 +++++++++++ .../AdaptiveTwoPaneCompactTest_e7711029_0.png | Bin 0 -> 7310 bytes ...AdaptiveTwoPaneExpandedTest_37699c78_0.png | Bin 0 -> 5055 bytes .../MainAppBarTest_748aa731_0.png | Bin 0 -> 68 bytes .../ScannedQrCodeDialogTest_748aa731_0.png | Bin 0 -> 68 bytes .../AutoLinkTextTest_748aa731_0.png | Bin 0 -> 8699 bytes .../ChannelSelectionTest_748aa731_0.png | Bin 0 -> 68 bytes .../NodeChipTest_748aa731_41783942_1.png | Bin 2262 -> 2140 bytes .../NodeChipTest_748aa731_41783942_2.png | Bin 1561 -> 1550 bytes .../NodeChipTest_748aa731_41783942_3.png | Bin 2250 -> 2275 bytes .../NodeChipTest_748aa731_41783942_4.png | Bin 2013 -> 2140 bytes .../QrDialogTest_748aa731_0.png | Bin 0 -> 68 bytes .../SecurityIconTest_748aa731_0.png | Bin 0 -> 68 bytes .../TransportIconTest_748aa731_0.png | Bin 0 -> 68 bytes .../MessageItemTest_748aa731_0.png | Bin 0 -> 68 bytes .../ReactionRowTest_748aa731_0.png | Bin 0 -> 3744 bytes .../NodeInfoSignalTest_748aa731_0.png | Bin 0 -> 68 bytes .../NodeInfoSimpleTest_748aa731_0.png | Bin 0 -> 68 bytes .../NodeInfoStatusTest_748aa731_0.png | Bin 0 -> 68 bytes .../NodeInfoTest_748aa731_41783942_0.png | Bin 0 -> 68 bytes .../NodeInfoTest_748aa731_41783942_1.png | Bin 0 -> 68 bytes .../NodeInfoTest_748aa731_41783942_2.png | Bin 0 -> 68 bytes .../NodeInfoTest_748aa731_41783942_3.png | Bin 0 -> 68 bytes .../NodeInfoTest_748aa731_41783942_4.png | Bin 0 -> 68 bytes feature/messaging/build.gradle.kts | 14 ++- .../feature/messaging/MessageListPaged.kt | 5 +- .../MessageItemTest_748aa731_0.png | Bin 0 -> 68 bytes .../RawStringTest_748aa731_0.png | Bin 0 -> 2556 bytes .../SimpleBoxTest_748aa731_0.png | Bin 0 -> 842 bytes feature/node/build.gradle.kts | 9 ++ .../node/component/EnvironmentMetrics.kt | 2 + .../feature/node/component/InfoCardPreview.kt | 93 ----------------- .../feature/node/component/PowerMetrics.kt | 1 + .../component/TelemetricActionsSection.kt | 3 + .../feature/node/list/NodeListScreen.kt | 2 +- .../feature/node/metrics/NeighborInfoLog.kt | 2 +- .../feature/node/metrics/TracerouteLog.kt | 2 +- 50 files changed, 332 insertions(+), 138 deletions(-) rename feature/node/src/main/kotlin/org/meshtastic/feature/node/component/CooldownOutlinedIconButton.kt => core/ui/src/main/kotlin/org/meshtastic/core/ui/component/CooldownIconButton.kt (96%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui/component}/DeliveryInfoDialog.kt (94%) rename {feature/node/src/main/kotlin/org/meshtastic/feature/node => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/InfoCard.kt (96%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/MessageActions.kt (93%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/MessageActionsBottomSheet.kt (99%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/MessageBubble.kt (95%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/MessageItem.kt (98%) rename {feature/node/src/main/kotlin/org/meshtastic/feature/node => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/NodeItem.kt (98%) rename {feature/node/src/main/kotlin/org/meshtastic/feature/node => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/NodeStatusIcons.kt (98%) rename {feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging => core/ui/src/main/kotlin/org/meshtastic/core/ui}/component/Reaction.kt (97%) create mode 100644 core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/AppScreenshotTest.kt create mode 100644 core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/MessageItemScreenshotTest.kt create mode 100644 core/ui/src/screenshotTest/kotlin/org/meshtastic/core/ui/NodeItemScreenshotTest.kt create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneCompactTest_e7711029_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/AdaptiveTwoPaneExpandedTest_37699c78_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/MainAppBarTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/AppScreenshotTest/ScannedQrCodeDialogTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/AutoLinkTextTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/ChannelSelectionTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/QrDialogTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/SecurityIconTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/ComponentScreenshotTest/TransportIconTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/MessageItemTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/MessageItemScreenshotTest/ReactionRowTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSignalTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoSimpleTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoStatusTest_748aa731_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_0.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_1.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_2.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_3.png create mode 100644 core/ui/src/screenshotTestFdroidDebug/reference/org/meshtastic/core/ui/NodeItemScreenshotTest/NodeInfoTest_748aa731_41783942_4.png create mode 100644 feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/MessageItemTest_748aa731_0.png create mode 100644 feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/RawStringTest_748aa731_0.png create mode 100644 feature/messaging/src/screenshotTestDebug/reference/org/meshtastic/feature/messaging/MessageScreenshotTest/SimpleBoxTest_748aa731_0.png delete mode 100644 feature/node/src/main/kotlin/org/meshtastic/feature/node/component/InfoCardPreview.kt 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 0000000000000000000000000000000000000000..e697fecaa06faa5c2a6b12b434584685d568bc60 GIT binary patch literal 7310 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7cb8964465ee17eed993b88b7641dfc20860133d GIT binary patch literal 5055 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bd01926438efcc452601dc179e9ac6512a671cd8 GIT binary patch literal 68 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 x^7l*%*pp8kLA;l{I7vxP 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 Y1z+5U3-~vk=Kufz07*qoM6N<$f)EN)NB{r; 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 6244babaf813e726e02ce464fe3fd7447a869c40..de1ff97dc21bcde5a3f9bc4757705863a2aa23bb 100644 GIT binary patch delta 1532 zcmYjRc{J1u6dq0CT=$wP z?Kz5=gs_Zbkz{IEN&^~RUbNoF>SRa^hGr;!AysNno!-uaOAkDoX7pLryZ7iOG%G8B zN#nEs0p=${Iu+YV+Cht7@)@DK5#wyDl#Qt6@YkLU>cm`%B(N04$()&3tR3!oheRT+ z4Bid^UkXD?_~zMlX2x!F#qt00xr%Q%Z@p8fK0Lu5Lt|Pneld(GE6TX#t76V6mEB7U zPm>FSwpXrGzbQhXahPeJ2Khk8G?wt-$m01ZeTt(n{Qf3y{fG3rD!dY}@n>_}Hcr2_ zI1lpy*BCU2n8U22wD>tltBV+3To%g*q2qx}88smh2YJ8GXsoc}P$n}{mFC7V<7-@+ z7TH38mlx!oE{I0RB>XTmoVdt#t?Iu*M{gZrPliWy6FE8ON`6|cp2BZA8KlIm-(v#T1%6pc-OHhKx zD@j~$c5sOdciSYRWjVcCSJZva#&InG(iFmE$Vxi#8@h6cQntv!&nq^+p#87mDX=#8rP?P4wzCX^eIuVv-Uf@keU zxsRc8*UPK1dcQeMdia{Y*4o-SVxH0?fZ(z}^02OWbVom2T`GGIS0!XHSNE~0{Kal) z<+);>V9!a*=Es ziQGXJV5_3BcwU2b?n39FnK%q0=A1_TX<8qyBXKb2&AK^2Ve<=sL^W79$8-}?G-0$e z`#08<*Sbg`dt%l!VO@I{*W?0TW|KbDH|Qv-NEsOR0|uJBN$h8rAp~|l0=$v@W1h}c z^6hYRnD4}@O4YEUnG@kb<9#KZV*SLQjxjsz5tCLh!deeqXIyzd>VTXZb&RufpL0e=&k z5Rn=miZKR!{$gp1VF~08E_2R$i*?xXXy07lHFX`opD^oN1zgxXkL{Ol9DWL{CBe4w zTHQG)Ta(dzC8BZv8(pka@sn;qIlCW%O+Qd=qZNL{i-~B+h9uRWy@;n;l6(5_!DC z(-jS0tjz~xPB9puzzybqYqEfe-*L7_J>_(p&G13_`vhf}glj_`wqpBt?{ zT9^fJK+43>kH1B7Yy7GE2<6Ar_duB(5|wH}6hDSDb7cWN>WNc>0c@o-BHPD+D4=ZB zfrAIq`XL0lv{0;)2E!E+g?d(-;h-PI7j_9GJMggTz&YcX*LLwrue6pj{Xs%xbCo1_ z8w$Lv2Odx%rdP1faq?JH(OE9q-mHcig z@ccIUaKF(c8+MJqa?)tNjkpVGM6fqYU_@xSH)$}!S}%S8&tT&o=QzT%ZOTZ~=Fu@z z4~y@<;}_X%k(qaj(^Cb?1i28ymKALno2?Vr3VJzb6c){G!9@4)*}AGANJPgy-HH2M z)RO)GSU@AUs7c3w(7kdr+KlH|s=%fM8+Ste%v=CA+TmyU#t?%q z{FL);Is4}t;KZ^s>wQCJl(#paXtJZ0cZ*_<#zf5Gc zo!pWpy34&wUO)0bRUl)#<(u1&H{30vCSSAD(*o91A&NI?;)i<%0zAvVlR>dzgfuIu zr_yt4Cndwe9ciKZg*(Dv1~`jwm;rWdUF=!xSY{Qpxhkg$T2o-j%zsv3xmp{(;8g<( zGhXPs(b&|Gy@`aj&;>(OVrw2smhL!<8Z9W~%pa@vM~?vm$C1>)(#btoS`J1M(b9VTl;RH=LVUUqZBxw3BGT&wL49|rmL#Rf zWK(;7@?iKZFP>IeQj#GB0H!AE%K@$D^GWDxKIJ8%F^fn|_sM0*rb@*;P;v8v`VK46 zvPNq>t?br8xB0%?nvpYYBVr!dWh98HAKUtG!1g0*ecdCe_?>uEL;Uybp;S8VpyM~%pfX@LnB!`(rOgK|6UTI~`BPX7<7j2dVF 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 41ce867ae37fd64afff033c5ce14e47726807e41..7454e8756496acde737a9f68c05d0b7b95ea1e7c 100644 GIT binary patch delta 2263 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 M07*qoM6N<$f@-r*M*si- 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 d032a70d54673e5cabe2bce5e960101a1ed481db..9a094dedfd322b31bc5490e90564da80c58eeaec 100644 GIT binary patch delta 2128 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 i1bW>1)||_ma`+z#^>7Q`0Gwt30000FH_vGr!;a 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 KMNUMnLSTXcyhft{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bd01926438efcc452601dc179e9ac6512a671cd8 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blZci7-kcznE1O_Gs237_}Hj&cB PKw$ { 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 0000000000000000000000000000000000000000..bd01926438efcc452601dc179e9ac6512a671cd8 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blZci7-kcznE1O_Gs237_}Hj&cB PKw$|2&>+4m*ccUiOV`}$?{ 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 Y^uJV9iO(e`fq8_%)78&qol`;+0Exu>asU7T literal 0 HcmV?d00001 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