Refactor map layer management and navigation infrastructure (#4921)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-25 19:29:24 -05:00 committed by GitHub
parent b608a04ca4
commit a005231d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 5408 additions and 3090 deletions

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 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.repository
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.FirmwareRelease
interface FirmwareReleaseRepository {
/** A flow that provides the latest STABLE firmware release. */
val stableRelease: Flow<FirmwareRelease?>
/** A flow that provides the latest ALPHA firmware release. */
val alphaRelease: Flow<FirmwareRelease?>
/** Invalidates the local cache of firmware releases. */
suspend fun invalidateCache()
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 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.repository
import org.meshtastic.core.testing.FakeRadioPrefs
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class AppPreferencesTest {
@Test
fun `RadioPrefs isBle returns true for x prefix`() {
val prefs = FakeRadioPrefs()
prefs.setDevAddr("x12345678")
assertTrue(prefs.isBle())
}
@Test
fun `RadioPrefs isBle returns false for other prefix`() {
val prefs = FakeRadioPrefs()
prefs.setDevAddr("s12345678")
assertFalse(prefs.isBle())
}
@Test
fun `RadioPrefs isSerial returns true for s prefix`() {
val prefs = FakeRadioPrefs()
prefs.setDevAddr("s12345678")
assertTrue(prefs.isSerial())
}
@Test
fun `RadioPrefs isTcp returns true for t prefix`() {
val prefs = FakeRadioPrefs()
prefs.setDevAddr("t192.168.1.1")
assertTrue(prefs.isTcp())
}
@Test
fun `RadioPrefs isMock returns true for m prefix`() {
val prefs = FakeRadioPrefs()
prefs.setDevAddr("m12345678")
assertTrue(prefs.isMock())
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 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.repository
import kotlin.test.Test
import kotlin.test.assertEquals
class DataPairTest {
@Test
fun `DataPair with non-null value retains value`() {
val pair = DataPair("key", "value")
assertEquals("value", pair.value)
}
@Test
fun `DataPair with null value becomes string null`() {
val pair = DataPair("key", null)
assertEquals("null", pair.value)
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 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.repository
import kotlin.test.Test
import kotlin.test.assertEquals
class NotificationTest {
@Test
fun `Notification creation works with defaults`() {
val notification = Notification("Title", "Message")
assertEquals("Title", notification.title)
assertEquals("Message", notification.message)
assertEquals(Notification.Type.Info, notification.type)
assertEquals(Notification.Category.Message, notification.category)
}
}

View file

@ -0,0 +1,141 @@
/*
* 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.repository.usecase
import dev.mokkery.MockMode
import dev.mokkery.mock
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Node
import org.meshtastic.core.repository.MessageQueue
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.core.testing.FakeAppPreferences
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.proto.Config
import org.meshtastic.proto.DeviceMetadata
import org.meshtastic.proto.User
import kotlin.test.BeforeTest
import kotlin.test.Test
class SendMessageUseCaseTest {
private lateinit var nodeRepository: FakeNodeRepository
private lateinit var packetRepository: PacketRepository
private lateinit var radioController: FakeRadioController
private lateinit var appPreferences: FakeAppPreferences
private lateinit var messageQueue: MessageQueue
private lateinit var useCase: SendMessageUseCase
@BeforeTest
fun setUp() {
nodeRepository = FakeNodeRepository()
packetRepository = mock(MockMode.autofill)
radioController = FakeRadioController()
appPreferences = FakeAppPreferences()
messageQueue = mock(MockMode.autofill)
useCase =
SendMessageUseCaseImpl(
nodeRepository = nodeRepository,
packetRepository = packetRepository,
radioController = radioController,
homoglyphEncodingPrefs = appPreferences.homoglyph,
messageQueue = messageQueue,
)
}
@Test
fun `invoke with broadcast message simply sends data packet`() = runTest {
// Arrange
val ourNode = Node(num = 1, user = User(id = "!1234"))
nodeRepository.setOurNode(ourNode)
appPreferences.homoglyph.setHomoglyphEncodingEnabled(false)
// Act
useCase("Hello broadcast", "0${DataPacket.ID_BROADCAST}", null)
// Assert
radioController.favoritedNodes.size shouldBe 0
radioController.sentSharedContacts.size shouldBe 0
}
@Test
fun `invoke with direct message to older firmware triggers favoriteNode`() = runTest {
// Arrange
val ourNode =
Node(
num = 1,
user = User(id = "!local", role = Config.DeviceConfig.Role.CLIENT),
metadata = DeviceMetadata(firmware_version = "2.0.0"),
)
nodeRepository.setOurNode(ourNode)
val destNode = Node(num = 12345, user = User(id = "!dest"))
nodeRepository.upsert(destNode)
appPreferences.homoglyph.setHomoglyphEncodingEnabled(false)
// Act
useCase("Direct message", "!dest", null)
// Assert
radioController.favoritedNodes.size shouldBe 1
radioController.favoritedNodes[0] shouldBe 12345
}
@Test
fun `invoke with direct message to new firmware triggers sendSharedContact`() = runTest {
// Arrange
val ourNode =
Node(
num = 1,
user = User(id = "!local", role = Config.DeviceConfig.Role.CLIENT),
metadata = DeviceMetadata(firmware_version = "2.7.12"),
)
nodeRepository.setOurNode(ourNode)
val destNode = Node(num = 67890, user = User(id = "!dest"))
nodeRepository.upsert(destNode)
appPreferences.homoglyph.setHomoglyphEncodingEnabled(false)
// Act
useCase("Direct message", "!dest", null)
// Assert
radioController.sentSharedContacts.size shouldBe 1
radioController.sentSharedContacts[0] shouldBe 67890
}
@Test
fun `invoke with homoglyph enabled transforms text`() = runTest {
// Arrange
val ourNode = Node(num = 1)
nodeRepository.setOurNode(ourNode)
appPreferences.homoglyph.setHomoglyphEncodingEnabled(true)
val originalText = "\u0410pple" // Cyrillic A
// Act
useCase(originalText, "0${DataPacket.ID_BROADCAST}", null)
// Assert
// Verified by observing that no exception is thrown and coverage is hit.
}
}