feat: Add mute node functionality (#4181)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-01-10 15:35:01 -06:00 committed by GitHub
parent 42fe7e9b2e
commit a67b519abd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 2174 additions and 458 deletions

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
/**
* Defines the capabilities and feature support based on the device firmware version.
*
* This class provides a centralized way to check if specific features are supported by the connected node's firmware.
* Add new features here to ensure consistency across the app.
*/
data class Capabilities(val firmwareVersion: String?) {
private val version = firmwareVersion?.let { DeviceVersion(it) }
/**
* Ability to mute notifications from specific nodes via admin messages.
*
* Note: This is currently not available in firmware but defined here for future support.
*/
val canMuteNode: Boolean
get() = version != null && version >= DeviceVersion("2.8.0")
/** Ability to request neighbor information from other nodes. Supported since firmware v2.7.15. */
val canRequestNeighborInfo: Boolean
get() = version != null && version >= DeviceVersion("2.7.15")
/** Ability to send verified shared contacts. Supported since firmware v2.7.12. */
val canSendVerifiedContacts: Boolean
get() = version != null && version >= DeviceVersion("2.7.12")
/** Ability to toggle device telemetry globally via module config. Supported since firmware v2.7.12. */
val canToggleTelemetryEnabled: Boolean
get() = version != null && version >= DeviceVersion("2.7.12")
/** Ability to toggle the 'is_unmessageable' flag in user config. Supported since firmware v2.6.9. */
val canToggleUnmessageable: Boolean
get() = version != null && version >= DeviceVersion("2.6.9")
/** Support for sharing contact information via QR codes. Supported since firmware v2.6.8. */
val supportsQrCodeSharing: Boolean
get() = version != null && version >= DeviceVersion("2.6.8")
}

View file

@ -16,7 +16,6 @@
*/
package org.meshtastic.core.model.util
import android.widget.EditText
import org.meshtastic.core.model.BuildConfig
import org.meshtastic.proto.ConfigProtos
import org.meshtastic.proto.MeshProtos
@ -66,18 +65,6 @@ fun Any.toPIIString() = if (!BuildConfig.DEBUG) {
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
@Suppress("MagicNumber")
fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
val timeInMillis = lastSeenUnix * 1000L
return android.text.format.DateUtils.getRelativeTimeSpanString(
timeInMillis,
currentTimeMillis,
android.text.format.DateUtils.MINUTE_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE,
)
.toString()
}
private const val MPS_TO_KMPH = 3.6f
private const val KM_TO_MILES = 0.621371f
@ -92,13 +79,3 @@ fun Int.mpsToMph(): Float {
val mph = this * MPS_TO_KMPH * KM_TO_MILES
return mph
}
// Allows usage like email.onEditorAction(EditorInfo.IME_ACTION_NEXT, { confirm() })
fun EditText.onEditorAction(actionId: Int, func: () -> Unit) {
setOnEditorActionListener { _, receivedActionId, _ ->
if (actionId == receivedActionId) {
func()
}
true
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CapabilitiesTest {
@Test
fun `canMuteNode requires v2 8 0`() {
assertFalse(Capabilities("2.7.15").canMuteNode)
assertFalse(Capabilities("2.7.99").canMuteNode)
assertTrue(Capabilities("2.8.0").canMuteNode)
assertTrue(Capabilities("2.8.1").canMuteNode)
}
@Test
fun `canRequestNeighborInfo requires v2 7 15`() {
assertFalse(Capabilities("2.7.14").canRequestNeighborInfo)
assertTrue(Capabilities("2.7.15").canRequestNeighborInfo)
assertTrue(Capabilities("2.8.0").canRequestNeighborInfo)
}
@Test
fun `canSendVerifiedContacts requires v2 7 12`() {
assertFalse(Capabilities("2.7.11").canSendVerifiedContacts)
assertTrue(Capabilities("2.7.12").canSendVerifiedContacts)
assertTrue(Capabilities("2.7.15").canSendVerifiedContacts)
}
@Test
fun `canToggleTelemetryEnabled requires v2 7 12`() {
assertFalse(Capabilities("2.7.11").canToggleTelemetryEnabled)
assertTrue(Capabilities("2.7.12").canToggleTelemetryEnabled)
}
@Test
fun `canToggleUnmessageable requires v2 6 9`() {
assertFalse(Capabilities("2.6.8").canToggleUnmessageable)
assertTrue(Capabilities("2.6.9").canToggleUnmessageable)
}
@Test
fun `supportsQrCodeSharing requires v2 6 8`() {
assertFalse(Capabilities("2.6.7").supportsQrCodeSharing)
assertTrue(Capabilities("2.6.8").supportsQrCodeSharing)
}
@Test
fun `null firmware returns all false`() {
val caps = Capabilities(null)
assertFalse(caps.canMuteNode)
assertFalse(caps.canRequestNeighborInfo)
assertFalse(caps.canSendVerifiedContacts)
assertFalse(caps.canToggleTelemetryEnabled)
assertFalse(caps.canToggleUnmessageable)
assertFalse(caps.supportsQrCodeSharing)
}
@Test
fun `invalid firmware returns all false`() {
val caps = Capabilities("invalid")
assertFalse(caps.canMuteNode)
assertFalse(caps.canRequestNeighborInfo)
assertFalse(caps.canSendVerifiedContacts)
assertFalse(caps.canToggleTelemetryEnabled)
assertFalse(caps.canToggleUnmessageable)
assertFalse(caps.supportsQrCodeSharing)
}
}