From 0b5a97d6ae12f07d04da1a3568d8d0fc3b5113bc Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 11 Jul 2025 11:32:52 +1000 Subject: [PATCH 1/4] add a skip value, so we can run and skip tests at the same level. --- .github/workflows/pull-request.yml | 11 ++++++++--- .github/workflows/reusable-android-build.yml | 6 ++++++ .github/workflows/reusable-android-test.yml | 6 ++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 60b735e75..7722002ea 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -15,22 +15,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Always pass for scheduled-updates - run: echo Scheduled updates branch, auto-passing by default. + run: | + echo Scheduled updates branch, auto-passing by default. + echo We could add an xml linter here. build_and_detekt: - if: github.repository == 'meshtastic/Meshtastic-Android' && github.head_ref != 'scheduled-updates' + if: github.repository == 'meshtastic/Meshtastic-Android' uses: ./.github/workflows/reusable-android-build.yml + with: + skip_tests: ${{ github.head_ref != 'scheduled-updates' }} secrets: GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # inputs.upload_artifacts defaults to true, so no need to specify for PRs androidTest: # AssumingandroidTest should also only run for the main repository - if: github.repository == 'meshtastic/Meshtastic-Android' && github.head_ref != 'scheduled-updates' + if: github.repository == 'meshtastic/Meshtastic-Android' uses: ./.github/workflows/reusable-android-test.yml with: api_levels: '[35]' # Run only on API 35 for PRs + skip_tests: ${{ github.head_ref != 'scheduled-updates' }} # upload_artifacts defaults to true, so no need to explicitly set secrets: GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} diff --git a/.github/workflows/reusable-android-build.yml b/.github/workflows/reusable-android-build.yml index 0f0905286..751d3c5e3 100644 --- a/.github/workflows/reusable-android-build.yml +++ b/.github/workflows/reusable-android-build.yml @@ -8,6 +8,11 @@ on: required: false type: boolean default: true + skip_tests: + description: 'Whether to skip running tests' + required: false + type: boolean + default: false secrets: GRADLE_ENCRYPTION_KEY: required: false @@ -16,6 +21,7 @@ jobs: build_and_detekt: runs-on: ubuntu-latest timeout-minutes: 35 + if: ${{ !inputs.skip_tests }} steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/reusable-android-test.yml b/.github/workflows/reusable-android-test.yml index 1056a0202..99f3ed96f 100644 --- a/.github/workflows/reusable-android-test.yml +++ b/.github/workflows/reusable-android-test.yml @@ -13,12 +13,18 @@ on: required: false type: string default: '[26, 35]' # Default to running both if not specified by caller + skip_tests: + description: 'Whether to skip running tests' + required: false + type: boolean + default: false secrets: GRADLE_ENCRYPTION_KEY: required: false jobs: androidTest: + if: ${{ !inputs.skip_tests }} runs-on: ubuntu-latest timeout-minutes: 25 strategy: From 61c7429edcc7e153c97fcb0bfe5b4faf31d66ae7 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 11 Jul 2025 11:35:49 +1000 Subject: [PATCH 2/4] fix polarity --- .github/workflows/pull-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7722002ea..535c3995f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -24,7 +24,7 @@ jobs: if: github.repository == 'meshtastic/Meshtastic-Android' uses: ./.github/workflows/reusable-android-build.yml with: - skip_tests: ${{ github.head_ref != 'scheduled-updates' }} + skip_tests: ${{ github.head_ref == 'scheduled-updates' }} secrets: GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} # inputs.upload_artifacts defaults to true, so no need to specify for PRs @@ -35,7 +35,7 @@ jobs: uses: ./.github/workflows/reusable-android-test.yml with: api_levels: '[35]' # Run only on API 35 for PRs - skip_tests: ${{ github.head_ref != 'scheduled-updates' }} + skip_tests: ${{ github.head_ref == 'scheduled-updates' }} # upload_artifacts defaults to true, so no need to explicitly set secrets: GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} From 8fe6bb0fee6589c8508c855d18571377b63f5629 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Sat, 12 Jul 2025 12:08:02 +1000 Subject: [PATCH 3/4] adding viamqtt icon --- .../com/geeksville/mesh/database/entity/Packet.kt | 3 ++- .../main/java/com/geeksville/mesh/model/Message.kt | 1 + .../mesh/ui/message/components/MessageItem.kt | 12 ++++++++++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt index 1a9d67254..d1a9e0589 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -51,7 +51,8 @@ data class PacketEntity( routingError = routingError, packetId = packetId, emojis = reactions.toReaction(getNode), - replyId = data.replyId + replyId = data.replyId, + viaMqtt = node.viaMqtt ) } } diff --git a/app/src/main/java/com/geeksville/mesh/model/Message.kt b/app/src/main/java/com/geeksville/mesh/model/Message.kt index 983215f82..c3a16178a 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -62,6 +62,7 @@ data class Message( val hopsAway: Int, val replyId: Int?, val originalMessage: Message? = null, + val viaMqtt: Boolean = false, ) { fun getStatusStringRes(): Pair { val title = if (routingError > 0) R.string.error else R.string.message_delivery_status diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt index 7363210a6..54e2a1608 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt @@ -27,7 +27,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cloud import androidx.compose.material.icons.filled.FormatQuote import androidx.compose.material3.Card import androidx.compose.material3.CardColors @@ -140,6 +142,13 @@ internal fun MessageItem( style = MaterialTheme.typography.labelMedium, modifier = Modifier.weight(1f, fill = true) ) + if (message.viaMqtt) { + Icon( + Icons.Default.Cloud, + contentDescription = stringResource(R.string.via_mqtt), + modifier = Modifier.size(MaterialTheme.typography.labelMedium.fontSize) + ) + } MessageActions( isLocal = message.fromLocal, status = message.status, @@ -275,6 +284,7 @@ private fun MessageItemPreview() { packetId = 4545, emojis = listOf(), replyId = null, + viaMqtt = false, ) val received = Message( text = "This is a received message", @@ -292,6 +302,7 @@ private fun MessageItemPreview() { packetId = 4545, emojis = listOf(), replyId = null, + viaMqtt = false, ) val receivedWithOriginalMessage = Message( text = "This is a received message w/ original, this is a longer message to test next-lining.", @@ -310,6 +321,7 @@ private fun MessageItemPreview() { emojis = listOf(), replyId = null, originalMessage = received, + viaMqtt = true, ) AppTheme { Column( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0ac6796f..3f1d403b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Hops away Last heard via MQTT + via MQTT via Favorite Unrecognized From 6bc822f83ae1a6f32f5eb32e459f8d41667906f8 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Sat, 12 Jul 2025 12:16:05 +1000 Subject: [PATCH 4/4] via mqtt last files --- .../mesh/compose/MessageItemTest.kt | 117 ++++++++++++++++++ .../mesh/ui/message/components/MessageItem.kt | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 app/src/androidTest/java/com/geeksville/mesh/compose/MessageItemTest.kt diff --git a/app/src/androidTest/java/com/geeksville/mesh/compose/MessageItemTest.kt b/app/src/androidTest/java/com/geeksville/mesh/compose/MessageItemTest.kt new file mode 100644 index 000000000..a87243500 --- /dev/null +++ b/app/src/androidTest/java/com/geeksville/mesh/compose/MessageItemTest.kt @@ -0,0 +1,117 @@ +/* + * 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 com.geeksville.mesh.compose + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.geeksville.mesh.MessageStatus +import com.geeksville.mesh.R +import com.geeksville.mesh.model.Message +import com.geeksville.mesh.model.Node +import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider +import com.geeksville.mesh.ui.message.components.MessageItem +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MessageItemTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun mqttIconIsDisplayedWhenViaMqttIsTrue() { + val testNode = NodePreviewParameterProvider().minnieMouse + val messageWithMqtt = Message( + text = "Test message via MQTT", + time = "10:00", + fromLocal = false, + status = MessageStatus.RECEIVED, + snr = 2.5f, + rssi = 90, + hopsAway = 0, + uuid = 1L, + receivedTime = System.currentTimeMillis(), + node = testNode, + read = false, + routingError = 0, + packetId = 1234, + emojis = listOf(), + replyId = null, + viaMqtt = true + ) + + composeTestRule.setContent { + MessageItem( + message = messageWithMqtt, + node = testNode, + selected = false, + onClick = {}, + onLongClick = {}, + onStatusClick = {}, + isConnected = true, + ourNode = testNode, + ) + } + + // Check that the MQTT icon is displayed + composeTestRule.onNodeWithContentDescription("via MQTT").assertIsDisplayed() + } + + @Test + fun mqttIconIsNotDisplayedWhenViaMqttIsFalse() { + val testNode = NodePreviewParameterProvider().minnieMouse + val messageWithoutMqtt = Message( + text = "Test message not via MQTT", + time = "10:00", + fromLocal = false, + status = MessageStatus.RECEIVED, + snr = 2.5f, + rssi = 90, + hopsAway = 0, + uuid = 1L, + receivedTime = System.currentTimeMillis(), + node = testNode, + read = false, + routingError = 0, + packetId = 1234, + emojis = listOf(), + replyId = null, + viaMqtt = false + ) + + composeTestRule.setContent { + MessageItem( + message = messageWithoutMqtt, + node = testNode, + selected = false, + onClick = {}, + onLongClick = {}, + onStatusClick = {}, + isConnected = true, + ourNode = testNode, + ) + } + + // Check that the MQTT icon is not displayed + composeTestRule.onNodeWithContentDescription("via MQTT").assertDoesNotExist() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt index 54e2a1608..d97091473 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt @@ -146,7 +146,7 @@ internal fun MessageItem( Icon( Icons.Default.Cloud, contentDescription = stringResource(R.string.via_mqtt), - modifier = Modifier.size(MaterialTheme.typography.labelMedium.fontSize) + modifier = Modifier.size(16.dp) ) } MessageActions(