mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat/decoupling (#4685)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
40244f8337
commit
2c49db8041
254 changed files with 5132 additions and 2666 deletions
|
|
@ -37,19 +37,20 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.data.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.datastore.BootloaderWarningDataSource
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.prefs.radio.RadioPrefs
|
||||
import org.meshtastic.core.prefs.radio.isBle
|
||||
import org.meshtastic.core.prefs.radio.isSerial
|
||||
import org.meshtastic.core.prefs.radio.isTcp
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.firmware_update_battery_low
|
||||
import org.meshtastic.core.resources.firmware_update_copying
|
||||
|
|
@ -72,7 +73,6 @@ import org.meshtastic.core.resources.firmware_update_unknown_hardware
|
|||
import org.meshtastic.core.resources.firmware_update_updating
|
||||
import org.meshtastic.core.resources.firmware_update_validating
|
||||
import org.meshtastic.core.resources.unknown
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ constructor(
|
|||
private val firmwareReleaseRepository: FirmwareReleaseRepository,
|
||||
private val deviceHardwareRepository: DeviceHardwareRepository,
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val radioPrefs: RadioPrefs,
|
||||
private val bootloaderWarningDataSource: BootloaderWarningDataSource,
|
||||
private val firmwareUpdateManager: FirmwareUpdateManager,
|
||||
|
|
@ -106,6 +106,8 @@ constructor(
|
|||
private val _state = MutableStateFlow<FirmwareUpdateState>(FirmwareUpdateState.Idle)
|
||||
val state: StateFlow<FirmwareUpdateState> = _state.asStateFlow()
|
||||
|
||||
val connectionState = radioController.connectionState
|
||||
|
||||
private val _selectedReleaseType = MutableStateFlow(FirmwareReleaseType.STABLE)
|
||||
val selectedReleaseType: StateFlow<FirmwareReleaseType> = _selectedReleaseType.asStateFlow()
|
||||
|
||||
|
|
@ -429,14 +431,14 @@ constructor(
|
|||
// Trigger a fresh connection attempt by MeshService
|
||||
address?.let { currentAddr ->
|
||||
Logger.i { "Post-update: Requesting MeshService to reconnect to $currentAddr" }
|
||||
serviceRepository.meshService?.setDeviceAddress("$DFU_RECONNECT_PREFIX$currentAddr")
|
||||
radioController.setDeviceAddress("$DFU_RECONNECT_PREFIX$currentAddr")
|
||||
}
|
||||
|
||||
// Wait for device to reconnect and settle
|
||||
val result =
|
||||
withTimeoutOrNull(VERIFY_TIMEOUT) {
|
||||
// Wait for both Connected state and node info to be present
|
||||
serviceRepository.connectionState.first { it is ConnectionState.Connected }
|
||||
connectionState.first { it is ConnectionState.Connected }
|
||||
nodeRepository.ourNodeInfo.filterNotNull().first()
|
||||
delay(VERIFY_DELAY) // Extra buffer for initial config sync
|
||||
true
|
||||
|
|
@ -462,7 +464,7 @@ constructor(
|
|||
return !isBatteryLow
|
||||
}
|
||||
|
||||
private suspend fun getDeviceHardware(ourNode: MyNodeEntity): DeviceHardware? {
|
||||
private suspend fun getDeviceHardware(ourNode: MyNodeInfo): DeviceHardware? {
|
||||
val nodeInfo = nodeRepository.ourNodeInfo.value
|
||||
val hwModelInt = nodeInfo?.user?.hw_model?.value
|
||||
val target = ourNode.pioEnv
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ import no.nordicsemi.android.dfu.DfuServiceListenerHelper
|
|||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.firmware_update_downloading_percent
|
||||
import org.meshtastic.core.resources.firmware_update_nordic_failed
|
||||
import org.meshtastic.core.resources.firmware_update_not_found_in_release
|
||||
import org.meshtastic.core.resources.firmware_update_starting_service
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class NordicDfuHandler
|
|||
constructor(
|
||||
private val firmwareRetriever: FirmwareRetriever,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
) : FirmwareUpdateHandler {
|
||||
|
||||
override suspend fun startUpdate(
|
||||
|
|
@ -113,7 +113,7 @@ constructor(
|
|||
updateState(FirmwareUpdateState.Processing(ProgressState(startingMsg)))
|
||||
|
||||
// n = Nordic (Legacy prefix handling in mesh service)
|
||||
serviceRepository.meshService?.setDeviceAddress("n")
|
||||
radioController.setDeviceAddress("n")
|
||||
|
||||
DfuServiceInitiator(address)
|
||||
.setDeviceName(deviceHardware.displayName)
|
||||
|
|
|
|||
|
|
@ -23,12 +23,13 @@ import kotlinx.coroutines.delay
|
|||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.firmware_update_downloading_percent
|
||||
import org.meshtastic.core.resources.firmware_update_rebooting
|
||||
import org.meshtastic.core.resources.firmware_update_retrieval_failed
|
||||
import org.meshtastic.core.resources.firmware_update_usb_failed
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -40,7 +41,8 @@ class UsbUpdateHandler
|
|||
@Inject
|
||||
constructor(
|
||||
private val firmwareRetriever: FirmwareRetriever,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val nodeRepository: NodeRepository,
|
||||
) : FirmwareUpdateHandler {
|
||||
|
||||
override suspend fun startUpdate(
|
||||
|
|
@ -62,8 +64,8 @@ constructor(
|
|||
|
||||
if (firmwareUri != null) {
|
||||
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
|
||||
val myNodeNum = serviceRepository.meshService?.getMyNodeInfo()?.myNodeNum ?: 0
|
||||
serviceRepository.meshService?.rebootToDfu(myNodeNum)
|
||||
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
|
||||
radioController.rebootToDfu(myNodeNum)
|
||||
delay(REBOOT_DELAY)
|
||||
|
||||
updateState(FirmwareUpdateState.AwaitingFileSave(null, "firmware.uf2", firmwareUri))
|
||||
|
|
@ -85,8 +87,8 @@ constructor(
|
|||
null
|
||||
} else {
|
||||
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
|
||||
val myNodeNum = serviceRepository.meshService?.getMyNodeInfo()?.myNodeNum ?: 0
|
||||
serviceRepository.meshService?.rebootToDfu(myNodeNum)
|
||||
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
|
||||
radioController.rebootToDfu(myNodeNum)
|
||||
delay(REBOOT_DELAY)
|
||||
|
||||
updateState(FirmwareUpdateState.AwaitingFileSave(firmwareFile, firmwareFile.name))
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ import android.net.Uri
|
|||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import no.nordicsemi.kotlin.ble.client.android.CentralManager
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.firmware_update_connecting_attempt
|
||||
import org.meshtastic.core.resources.firmware_update_downloading_percent
|
||||
|
|
@ -40,7 +44,6 @@ import org.meshtastic.core.resources.firmware_update_retrieval_failed
|
|||
import org.meshtastic.core.resources.firmware_update_starting_ota
|
||||
import org.meshtastic.core.resources.firmware_update_uploading
|
||||
import org.meshtastic.core.resources.firmware_update_waiting_reboot
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.feature.firmware.FirmwareRetriever
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateHandler
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateState
|
||||
|
|
@ -68,7 +71,8 @@ class Esp32OtaUpdateHandler
|
|||
@Inject
|
||||
constructor(
|
||||
private val firmwareRetriever: FirmwareRetriever,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val centralManager: CentralManager,
|
||||
@ApplicationContext private val context: Context,
|
||||
) : FirmwareUpdateHandler {
|
||||
|
|
@ -201,13 +205,11 @@ constructor(
|
|||
}
|
||||
|
||||
private fun triggerRebootOta(mode: Int, hash: ByteArray?) {
|
||||
val service = serviceRepository.meshService ?: return
|
||||
try {
|
||||
val myInfo = service.getMyNodeInfo() ?: return
|
||||
Logger.i { "ESP32 OTA: Triggering reboot OTA mode $mode with hash" }
|
||||
service.requestRebootOta(service.getPacketId(), myInfo.myNodeNum, mode, hash)
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
|
||||
Logger.e(e) { "ESP32 OTA: Failed to trigger reboot OTA" }
|
||||
val myInfo = nodeRepository.myNodeInfo.value ?: return
|
||||
val myNodeNum = myInfo.myNodeNum
|
||||
Logger.i { "ESP32 OTA: Triggering reboot OTA mode $mode with hash" }
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
radioController.requestRebootOta(radioController.getPacketId(), myNodeNum, mode, hash)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,12 +218,8 @@ constructor(
|
|||
* interface) cleanly disconnects without reconnection attempts.
|
||||
*/
|
||||
private fun disconnectMeshService() {
|
||||
try {
|
||||
Logger.i { "ESP32 OTA: Disconnecting mesh service for OTA" }
|
||||
serviceRepository.meshService?.setDeviceAddress("n")
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
|
||||
Logger.w(e) { "ESP32 OTA: Error disconnecting mesh service" }
|
||||
}
|
||||
Logger.i { "ESP32 OTA: Disconnecting mesh service for OTA" }
|
||||
radioController.setDeviceAddress("n")
|
||||
}
|
||||
|
||||
private suspend fun obtainFirmwareFile(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ import org.junit.Before
|
|||
import org.junit.Test
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.feature.firmware.FirmwareRetriever
|
||||
import org.meshtastic.feature.firmware.FirmwareUpdateState
|
||||
import java.io.IOException
|
||||
|
|
@ -42,12 +43,14 @@ import java.io.IOException
|
|||
class Esp32OtaUpdateHandlerTest {
|
||||
|
||||
private val firmwareRetriever: FirmwareRetriever = mockk()
|
||||
private val serviceRepository: ServiceRepository = mockk()
|
||||
private val radioController: RadioController = mockk()
|
||||
private val nodeRepository: NodeRepository = mockk()
|
||||
private val centralManager: CentralManager = mockk()
|
||||
private val context: Context = mockk()
|
||||
private val contentResolver: ContentResolver = mockk()
|
||||
|
||||
private val handler = Esp32OtaUpdateHandler(firmwareRetriever, serviceRepository, centralManager, context)
|
||||
private val handler =
|
||||
Esp32OtaUpdateHandler(firmwareRetriever, radioController, nodeRepository, centralManager, context)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
|
|
|
|||
|
|
@ -87,9 +87,8 @@ import org.meshtastic.core.common.gpsDisabled
|
|||
import org.meshtastic.core.common.util.DateFormatter
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.toString
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.calculating
|
||||
|
|
@ -344,7 +343,7 @@ fun MapView(
|
|||
|
||||
LaunchedEffect(selectedWaypointId, waypoints) {
|
||||
if (selectedWaypointId != null && waypoints.containsKey(selectedWaypointId)) {
|
||||
waypoints[selectedWaypointId]?.data?.waypoint?.let { pt ->
|
||||
waypoints[selectedWaypointId]?.waypoint?.let { pt ->
|
||||
val geoPoint = GeoPoint((pt.latitude_i ?: 0) * 1e-7, (pt.longitude_i ?: 0) * 1e-7)
|
||||
map.controller.setCenter(geoPoint)
|
||||
map.controller.setZoom(WAYPOINT_ZOOM)
|
||||
|
|
@ -496,7 +495,7 @@ fun MapView(
|
|||
fun showMarkerLongPressDialog(id: Int) {
|
||||
performHapticFeedback()
|
||||
Logger.d { "marker long pressed id=$id" }
|
||||
val waypoint = waypoints[id]?.data?.waypoint ?: return
|
||||
val waypoint = waypoints[id]?.waypoint ?: return
|
||||
// edit only when unlocked or lockedTo myNodeNum
|
||||
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
showEditWaypointDialog = waypoint
|
||||
|
|
@ -512,13 +511,13 @@ fun MapView(
|
|||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun MapView.onWaypointChanged(waypoints: Collection<Packet>, selectedWaypointId: Int?): List<MarkerWithLabel> {
|
||||
fun MapView.onWaypointChanged(waypoints: Collection<DataPacket>, selectedWaypointId: Int?): List<MarkerWithLabel> {
|
||||
return waypoints.mapNotNull { waypoint ->
|
||||
val pt = waypoint.data.waypoint ?: return@mapNotNull null
|
||||
val pt = waypoint.waypoint ?: return@mapNotNull null
|
||||
if (!mapFilterState.showWaypoints) return@mapNotNull null // Use collected mapFilterState
|
||||
val lock = if ((pt.locked_to ?: 0) != 0) "\uD83D\uDD12" else ""
|
||||
val time = DateFormatter.formatDateTime(waypoint.received_time)
|
||||
val label = (pt.name ?: "") + " " + formatAgo((waypoint.received_time / 1000).toInt())
|
||||
val time = DateFormatter.formatDateTime(waypoint.time)
|
||||
val label = (pt.name ?: "") + " " + formatAgo((waypoint.time / 1000).toInt())
|
||||
val emoji = String(Character.toChars(if ((pt.icon ?: 0) == 0) 128205 else pt.icon!!))
|
||||
val now = nowMillis
|
||||
val expireTimeMillis = (pt.expire ?: 0) * 1000L
|
||||
|
|
@ -530,7 +529,7 @@ fun MapView(
|
|||
}
|
||||
MarkerWithLabel(this, label, emoji).apply {
|
||||
id = "${pt.id}"
|
||||
title = "${pt.name} (${getUsername(waypoint.data.from)}$lock)"
|
||||
title = "${pt.name} (${getUsername(waypoint.from)}$lock)"
|
||||
snippet = "[$time] ${pt.description} " + getString(Res.string.expires) + ": $expireTimeStr"
|
||||
position = GeoPoint((pt.latitude_i ?: 0) * 1e-7, (pt.longitude_i ?: 0) * 1e-7)
|
||||
if (selectedWaypointId == pt.id) {
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import javax.inject.Inject
|
||||
|
|
@ -41,12 +41,12 @@ class MapViewModel
|
|||
constructor(
|
||||
mapPrefs: MapPrefs,
|
||||
packetRepository: PacketRepository,
|
||||
private val nodeRepository: NodeRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
override val nodeRepository: NodeRepository,
|
||||
radioController: RadioController,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
buildConfigProvider: BuildConfigProvider,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, serviceRepository) {
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, radioController) {
|
||||
|
||||
private val _selectedWaypointId = MutableStateFlow(savedStateHandle.toRoute<MapRoutes.Map>().waypointId)
|
||||
val selectedWaypointId: StateFlow<Int?> = _selectedWaypointId.asStateFlow()
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||
import org.json.JSONObject
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.metersIn
|
||||
import org.meshtastic.core.model.util.mpsToKmph
|
||||
import org.meshtastic.core.model.util.mpsToMph
|
||||
|
|
@ -272,7 +272,7 @@ fun MapView(
|
|||
|
||||
val allNodes by mapViewModel.nodesWithPosition.collectAsStateWithLifecycle(listOf())
|
||||
val waypoints by mapViewModel.waypoints.collectAsStateWithLifecycle(emptyMap())
|
||||
val displayableWaypoints = waypoints.values.mapNotNull { it.data.waypoint }
|
||||
val displayableWaypoints = waypoints.values.mapNotNull { it.waypoint }
|
||||
val selectedWaypointId by mapViewModel.selectedWaypointId.collectAsStateWithLifecycle()
|
||||
|
||||
val tracerouteSelection =
|
||||
|
|
|
|||
|
|
@ -45,14 +45,14 @@ import kotlinx.coroutines.withContext
|
|||
import kotlinx.serialization.Serializable
|
||||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.data.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.Config
|
||||
import java.io.File
|
||||
|
|
@ -86,11 +86,11 @@ constructor(
|
|||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
serviceRepository: ServiceRepository,
|
||||
radioController: RadioController,
|
||||
private val customTileProviderRepository: CustomTileProviderRepository,
|
||||
uiPreferencesDataSource: UiPreferencesDataSource,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, serviceRepository) {
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, radioController) {
|
||||
|
||||
private val _selectedWaypointId = MutableStateFlow(savedStateHandle.toRoute<MapRoutes.Map>().waypointId)
|
||||
val selectedWaypointId: StateFlow<Int?> = _selectedWaypointId.asStateFlow()
|
||||
|
|
@ -344,7 +344,7 @@ constructor(
|
|||
viewModelScope.launch {
|
||||
val wpMap = waypoints.first { it.containsKey(wpId) }
|
||||
wpMap[wpId]?.let { packet ->
|
||||
val waypoint = packet.data.waypoint!!
|
||||
val waypoint = packet.waypoint!!
|
||||
val latLng = LatLng((waypoint.latitude_i ?: 0) / 1e7, (waypoint.longitude_i ?: 0) / 1e7)
|
||||
cameraPositionState.position = CameraPosition.fromLatLngZoom(latLng, 15f)
|
||||
}
|
||||
|
|
@ -643,6 +643,9 @@ constructor(
|
|||
super.onCleared()
|
||||
(currentTileProvider as? MBTilesProvider)?.close()
|
||||
}
|
||||
|
||||
override fun getUser(userId: String?) =
|
||||
nodeRepository.getUser(userId ?: org.meshtastic.core.model.DataPacket.ID_BROADCAST)
|
||||
}
|
||||
|
||||
enum class LayerType {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.ui.component.NodeChip
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package org.meshtastic.feature.map.model
|
|||
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.maps.android.clustering.ClusterItem
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
data class NodeClusterItem(
|
||||
val node: Node,
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.map
|
||||
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -29,60 +27,45 @@ import kotlinx.coroutines.flow.mapLatest
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.util.TimeConstants
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.any
|
||||
import org.meshtastic.core.resources.eight_hours
|
||||
import org.meshtastic.core.resources.one_day
|
||||
import org.meshtastic.core.resources.one_hour
|
||||
import org.meshtastic.core.resources.two_days
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.feature.map.model.TracerouteOverlay
|
||||
import org.meshtastic.proto.Position
|
||||
import org.meshtastic.proto.User
|
||||
import org.meshtastic.proto.Waypoint
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
sealed class LastHeardFilter(val seconds: Long, val label: StringResource) {
|
||||
data object Any : LastHeardFilter(0L, Res.string.any)
|
||||
|
||||
data object OneHour : LastHeardFilter(TimeConstants.ONE_HOUR.inWholeSeconds, Res.string.one_hour)
|
||||
|
||||
data object EightHours : LastHeardFilter(TimeConstants.EIGHT_HOURS.inWholeSeconds, Res.string.eight_hours)
|
||||
|
||||
data object OneDay : LastHeardFilter(TimeConstants.ONE_DAY.inWholeSeconds, Res.string.one_day)
|
||||
|
||||
data object TwoDays : LastHeardFilter(TimeConstants.TWO_DAYS.inWholeSeconds, Res.string.two_days)
|
||||
|
||||
companion object {
|
||||
fun fromSeconds(seconds: Long): LastHeardFilter = entries.find { it.seconds == seconds } ?: Any
|
||||
|
||||
val entries = listOf(Any, OneHour, EightHours, OneDay, TwoDays)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
abstract class BaseMapViewModel(
|
||||
protected val mapPrefs: MapPrefs,
|
||||
private val nodeRepository: NodeRepository,
|
||||
protected open val nodeRepository: NodeRepository,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
) : ViewModel() {
|
||||
|
||||
val myNodeInfo = nodeRepository.myNodeInfo
|
||||
|
||||
val ourNodeInfo = nodeRepository.ourNodeInfo
|
||||
|
||||
val myNodeNum
|
||||
get() = myNodeInfo.value?.myNodeNum
|
||||
|
||||
val myId = nodeRepository.myId
|
||||
|
||||
val isConnected =
|
||||
radioController.connectionState
|
||||
.map { it is org.meshtastic.core.model.ConnectionState.Connected }
|
||||
.stateInWhileSubscribed(initialValue = false)
|
||||
|
||||
val nodes: StateFlow<List<Node>> =
|
||||
nodeRepository
|
||||
.getNodes()
|
||||
|
|
@ -94,79 +77,66 @@ abstract class BaseMapViewModel(
|
|||
.map { nodes -> nodes.filter { node -> node.validPosition != null } }
|
||||
.stateInWhileSubscribed(initialValue = emptyList())
|
||||
|
||||
val waypoints: StateFlow<Map<Int, Packet>> =
|
||||
val waypoints: StateFlow<Map<Int, DataPacket>> =
|
||||
packetRepository
|
||||
.getWaypoints()
|
||||
.mapLatest { list ->
|
||||
list
|
||||
.associateBy { packet -> packet.data.waypoint!!.id }
|
||||
.associateBy { packet -> packet.waypoint!!.id }
|
||||
.filterValues {
|
||||
val expire = it.data.waypoint!!.expire ?: 0
|
||||
val expire = it.waypoint?.expire ?: 0
|
||||
expire == 0 || expire.toLong() > nowSeconds
|
||||
}
|
||||
}
|
||||
.stateInWhileSubscribed(initialValue = emptyMap())
|
||||
|
||||
private val showOnlyFavorites = MutableStateFlow(mapPrefs.showOnlyFavorites)
|
||||
|
||||
private val showWaypointsOnMap = MutableStateFlow(mapPrefs.showWaypointsOnMap)
|
||||
|
||||
private val showPrecisionCircleOnMap = MutableStateFlow(mapPrefs.showPrecisionCircleOnMap)
|
||||
|
||||
private val lastHeardFilter = MutableStateFlow(LastHeardFilter.fromSeconds(mapPrefs.lastHeardFilter))
|
||||
|
||||
private val lastHeardTrackFilter = MutableStateFlow(LastHeardFilter.fromSeconds(mapPrefs.lastHeardTrackFilter))
|
||||
|
||||
fun setLastHeardFilter(filter: LastHeardFilter) {
|
||||
mapPrefs.lastHeardFilter = filter.seconds
|
||||
lastHeardFilter.value = filter
|
||||
}
|
||||
|
||||
fun setLastHeardTrackFilter(filter: LastHeardFilter) {
|
||||
mapPrefs.lastHeardTrackFilter = filter.seconds
|
||||
lastHeardTrackFilter.value = filter
|
||||
}
|
||||
|
||||
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
|
||||
|
||||
fun getNodeByNum(nodeNum: Int): Node? = nodeRepository.nodeDBbyNum.value[nodeNum]
|
||||
|
||||
open fun getUser(userId: String?): User = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
fun getUser(nodeNum: Int): User = nodeRepository.getUser(nodeNum)
|
||||
|
||||
fun getNodeOrFallback(nodeNum: Int): Node = getNodeByNum(nodeNum) ?: Node(num = nodeNum, user = getUser(nodeNum))
|
||||
|
||||
val isConnected =
|
||||
serviceRepository.connectionState.map { it.isConnected() }.stateInWhileSubscribed(initialValue = false)
|
||||
val showOnlyFavoritesOnMap = showOnlyFavorites
|
||||
|
||||
fun toggleOnlyFavorites() {
|
||||
val current = showOnlyFavorites.value
|
||||
mapPrefs.showOnlyFavorites = !current
|
||||
showOnlyFavorites.value = !current
|
||||
val newValue = !showOnlyFavorites.value
|
||||
showOnlyFavorites.value = newValue
|
||||
mapPrefs.showOnlyFavorites = newValue
|
||||
}
|
||||
|
||||
private val showWaypoints = MutableStateFlow(mapPrefs.showWaypointsOnMap)
|
||||
val showWaypointsOnMap = showWaypoints
|
||||
|
||||
fun toggleShowWaypointsOnMap() {
|
||||
val current = showWaypointsOnMap.value
|
||||
mapPrefs.showWaypointsOnMap = !current
|
||||
showWaypointsOnMap.value = !current
|
||||
val newValue = !showWaypoints.value
|
||||
showWaypoints.value = newValue
|
||||
mapPrefs.showWaypointsOnMap = newValue
|
||||
}
|
||||
|
||||
private val showPrecisionCircle = MutableStateFlow(mapPrefs.showPrecisionCircleOnMap)
|
||||
val showPrecisionCircleOnMap = showPrecisionCircle
|
||||
|
||||
fun toggleShowPrecisionCircleOnMap() {
|
||||
val current = showPrecisionCircleOnMap.value
|
||||
mapPrefs.showPrecisionCircleOnMap = !current
|
||||
showPrecisionCircleOnMap.value = !current
|
||||
val newValue = !showPrecisionCircle.value
|
||||
showPrecisionCircle.value = newValue
|
||||
mapPrefs.showPrecisionCircleOnMap = newValue
|
||||
}
|
||||
|
||||
fun generatePacketId(): Int? {
|
||||
return try {
|
||||
serviceRepository.meshService?.packetId
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e { "RemoteException: ${ex.message}" }
|
||||
return null
|
||||
}
|
||||
private val lastHeardFilterValue = MutableStateFlow(LastHeardFilter.fromSeconds(mapPrefs.lastHeardFilter))
|
||||
val lastHeardFilter = lastHeardFilterValue
|
||||
|
||||
fun setLastHeardFilter(filter: LastHeardFilter) {
|
||||
lastHeardFilterValue.value = filter
|
||||
mapPrefs.lastHeardFilter = filter.seconds
|
||||
}
|
||||
|
||||
private val lastHeardTrackFilterValue = MutableStateFlow(LastHeardFilter.fromSeconds(mapPrefs.lastHeardTrackFilter))
|
||||
val lastHeardTrackFilter = lastHeardTrackFilterValue
|
||||
|
||||
fun setLastHeardTrackFilter(filter: LastHeardFilter) {
|
||||
lastHeardTrackFilterValue.value = filter
|
||||
mapPrefs.lastHeardTrackFilter = filter.seconds
|
||||
}
|
||||
|
||||
abstract fun getUser(userId: String?): org.meshtastic.proto.User
|
||||
|
||||
fun getNodeOrFallback(nodeNum: Int): Node = nodeRepository.nodeDBbyNum.value[nodeNum] ?: Node(num = nodeNum)
|
||||
|
||||
fun deleteWaypoint(id: Int) = viewModelScope.launch(Dispatchers.IO) { packetRepository.deleteWaypoint(id) }
|
||||
|
||||
fun sendWaypoint(wpt: Waypoint, contactKey: String = "0${DataPacket.ID_BROADCAST}") {
|
||||
|
|
@ -179,13 +149,11 @@ abstract class BaseMapViewModel(
|
|||
}
|
||||
|
||||
private fun sendDataPacket(p: DataPacket) {
|
||||
try {
|
||||
serviceRepository.meshService?.send(p)
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e { "Send DataPacket error: ${ex.message}" }
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) { radioController.sendMessage(p) }
|
||||
}
|
||||
|
||||
fun generatePacketId(): Int = radioController.getPacketId()
|
||||
|
||||
data class MapFilterState(
|
||||
val onlyFavorites: Boolean,
|
||||
val showWaypoints: Boolean,
|
||||
|
|
@ -259,3 +227,17 @@ fun BaseMapViewModel.tracerouteNodeSelection(
|
|||
nodeLookup = nodesForLookup.associateBy { it.num },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
enum class LastHeardFilter(val label: StringResource, val seconds: Long) {
|
||||
Any(Res.string.any, 0L),
|
||||
OneHour(Res.string.one_hour, 3600L),
|
||||
EightHours(Res.string.eight_hours, 28800L),
|
||||
OneDay(Res.string.one_day, 86400L),
|
||||
TwoDays(Res.string.two_days, 172800L),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromSeconds(seconds: Long): LastHeardFilter = entries.find { it.seconds == seconds } ?: Any
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ import kotlinx.coroutines.flow.mapLatest
|
|||
import kotlinx.coroutines.flow.toList
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.ui.util.toPosition
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.feature.map.model.CustomTileSource
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.data.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -60,7 +60,7 @@ class MapViewModelTest {
|
|||
private val nodeRepository = mockk<NodeRepository>(relaxed = true)
|
||||
private val packetRepository = mockk<PacketRepository>(relaxed = true)
|
||||
private val radioConfigRepository = mockk<RadioConfigRepository>(relaxed = true)
|
||||
private val serviceRepository = mockk<ServiceRepository>(relaxed = true)
|
||||
private val radioController = mockk<RadioController>(relaxed = true)
|
||||
private val customTileProviderRepository = mockk<CustomTileProviderRepository>(relaxed = true)
|
||||
private val uiPreferencesDataSource = mockk<UiPreferencesDataSource>(relaxed = true)
|
||||
private val savedStateHandle = SavedStateHandle(mapOf("waypointId" to null))
|
||||
|
|
@ -81,7 +81,7 @@ class MapViewModelTest {
|
|||
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())
|
||||
every { nodeRepository.getNodes() } returns flowOf(emptyList())
|
||||
every { packetRepository.getWaypoints() } returns flowOf(emptyList())
|
||||
every { serviceRepository.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
viewModel =
|
||||
MapViewModel(
|
||||
|
|
@ -91,7 +91,7 @@ class MapViewModelTest {
|
|||
nodeRepository,
|
||||
packetRepository,
|
||||
radioConfigRepository,
|
||||
serviceRepository,
|
||||
radioController,
|
||||
customTileProviderRepository,
|
||||
uiPreferencesDataSource,
|
||||
savedStateHandle,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ import org.jetbrains.compose.resources.pluralStringResource
|
|||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.HomoglyphCharacterStringTransformer
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.alert_bell_text
|
||||
|
|
|
|||
|
|
@ -61,11 +61,10 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.new_messages_below
|
||||
import org.meshtastic.feature.messaging.component.MessageItem
|
||||
|
|
@ -545,7 +544,7 @@ private fun MessageStatusDialog(
|
|||
remember(message.relayNode, nodes, ourNode) {
|
||||
derivedStateOf {
|
||||
message.relayNode?.let { relayNodeId ->
|
||||
Packet.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
Node.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* 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
|
||||
|
|
@ -14,10 +14,9 @@
|
|||
* 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.feature.messaging
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
/** Defines the various user interactions that can occur on the MessageScreen. */
|
||||
internal sealed interface MessageScreenEvent {
|
||||
|
|
|
|||
|
|
@ -32,21 +32,21 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.SendMessageUseCase
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
|
||||
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ import androidx.compose.ui.unit.dp
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.filter_message_label
|
||||
import org.meshtastic.core.resources.message_delivery_status
|
||||
|
|
|
|||
|
|
@ -57,12 +57,11 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.getStringResFrom
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.model.getStringResFrom
|
||||
import org.meshtastic.core.model.util.getShortDateTime
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.delivery_confirmed
|
||||
|
|
@ -148,7 +147,9 @@ internal fun ReactionRow(
|
|||
|
||||
AnimatedVisibility(emojiGroups.isNotEmpty(), modifier = modifier) {
|
||||
LazyRow(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
items(emojiGroups.entries.toList()) { (emoji, reactions) ->
|
||||
items(emojiGroups.entries.toList()) { entry ->
|
||||
val emoji = entry.key
|
||||
val reactions = entry.value
|
||||
val localReaction = reactions.find { it.user.id == DataPacket.ID_LOCAL || it.user.id == myId }
|
||||
ReactionItem(
|
||||
emoji = emoji,
|
||||
|
|
@ -218,7 +219,7 @@ internal fun ReactionDialog(
|
|||
|
||||
val relayNodeName =
|
||||
reaction.relayNode?.let { relayNodeId ->
|
||||
Packet.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
Node.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
}
|
||||
|
||||
DeliveryInfo(
|
||||
|
|
@ -236,7 +237,9 @@ internal fun ReactionDialog(
|
|||
}
|
||||
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
items(groupedEmojis.entries.toList()) { (emoji, reactions) ->
|
||||
items(groupedEmojis.entries.toList()) { entry ->
|
||||
val emoji = entry.key
|
||||
val reactions = entry.value
|
||||
val localReaction = reactions.find { it.user.id == DataPacket.ID_LOCAL || it.user.id == myId }
|
||||
val isSending =
|
||||
localReaction?.status == MessageStatus.QUEUED || localReaction?.status == MessageStatus.ENROUTE
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.domain.MessageQueue
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.meshtastic.feature.messaging.domain.worker.WorkManagerMessageQueue
|
||||
|
||||
@Module
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import androidx.work.CoroutineWorker
|
|||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
|
||||
@HiltWorker
|
||||
class SendMessageWorker
|
||||
|
|
@ -47,18 +47,16 @@ constructor(
|
|||
return Result.retry()
|
||||
}
|
||||
|
||||
val packetEntity =
|
||||
val packetData =
|
||||
packetRepository.getPacketByPacketId(packetId)
|
||||
?: return Result.failure() // Packet no longer exists in DB? Do not retry.
|
||||
|
||||
val packetData = packetEntity.packet.data
|
||||
|
||||
return try {
|
||||
radioController.sendMessage(packetData)
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.ENROUTE)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.ERROR)
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.QUEUED)
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import org.meshtastic.core.domain.MessageQueue
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ import kotlinx.coroutines.launch
|
|||
import org.jetbrains.compose.resources.pluralStringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.model.Contact
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.util.TimeConstants
|
||||
import org.meshtastic.core.model.util.formatMuteRemainingTime
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.model.Contact
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
|
|
@ -59,7 +58,7 @@ constructor(
|
|||
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = ChannelSet())
|
||||
|
||||
// Combine node info and myId to reduce argument count in subsequent combines
|
||||
private val identityFlow: Flow<Pair<MyNodeEntity?, String?>> =
|
||||
private val identityFlow: Flow<Pair<MyNodeInfo?, String?>> =
|
||||
combine(nodeRepository.myNodeInfo, nodeRepository.myId) { info, id -> Pair(info, id) }
|
||||
|
||||
/**
|
||||
|
|
@ -78,42 +77,42 @@ constructor(
|
|||
settings,
|
||||
->
|
||||
val (myNodeInfo, myId) = identity
|
||||
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList()
|
||||
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList<Contact>()
|
||||
// Add empty channel placeholders (always show Broadcast contacts, even when empty)
|
||||
val placeholder =
|
||||
(0 until channelSet.settings.size).associate { ch ->
|
||||
val contactKey = "$ch${DataPacket.ID_BROADCAST}"
|
||||
val data = DataPacket(bytes = null, dataType = 1, time = 0L, channel = ch)
|
||||
contactKey to Packet(0L, myNodeNum, 1, contactKey, 0L, true, data)
|
||||
contactKey to data
|
||||
}
|
||||
|
||||
(contacts + (placeholder - contacts.keys)).values.collectionsMap { packet ->
|
||||
val data = packet.data
|
||||
val contactKey = packet.contact_key
|
||||
|
||||
(contacts + (placeholder - contacts.keys)).entries.collectionsMap { entry ->
|
||||
val contactKey = entry.key
|
||||
val packetData = entry.value
|
||||
// Determine if this is my message (originated on this device)
|
||||
val fromLocal = (data.from == DataPacket.ID_LOCAL || (myId != null && data.from == myId))
|
||||
val toBroadcast = data.to == DataPacket.ID_BROADCAST
|
||||
val fromLocal =
|
||||
(packetData.from == DataPacket.ID_LOCAL || (myId != null && packetData.from == myId))
|
||||
val toBroadcast = packetData.to == DataPacket.ID_BROADCAST
|
||||
|
||||
// grab usernames from NodeInfo
|
||||
val userId = if (fromLocal) data.to else data.from
|
||||
val userId = if (fromLocal) packetData.to else packetData.from
|
||||
val user = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
val node = nodeRepository.getNode(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
val shortName = user.short_name
|
||||
val longName =
|
||||
if (toBroadcast) {
|
||||
channelSet.getChannel(data.channel)?.name ?: "Channel ${data.channel}"
|
||||
channelSet.getChannel(packetData.channel)?.name ?: "Channel ${packetData.channel}"
|
||||
} else {
|
||||
user.long_name
|
||||
}
|
||||
|
||||
Contact(
|
||||
contactKey = contactKey,
|
||||
shortName = if (toBroadcast) data.channel.toString() else shortName,
|
||||
shortName = if (toBroadcast) packetData.channel.toString() else shortName,
|
||||
longName = longName,
|
||||
lastMessageTime = if (data.time != 0L) data.time else null,
|
||||
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
|
||||
lastMessageTime = if (packetData.time != 0L) packetData.time else null,
|
||||
lastMessageText = if (fromLocal) packetData.text else "$shortName: ${packetData.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKey),
|
||||
messageCount = packetRepository.getMessageCount(contactKey),
|
||||
isMuted = settings[contactKey]?.isMuted == true,
|
||||
|
|
@ -140,36 +139,41 @@ constructor(
|
|||
val myId = params.myId
|
||||
|
||||
packetRepository.getContactsPaged().map { pagingData ->
|
||||
pagingData.map { packet ->
|
||||
val data = packet.data
|
||||
val contactKey = packet.contact_key
|
||||
pagingData.map { packetData: DataPacket ->
|
||||
val contactKey =
|
||||
"${packetData.channel}${packetData.to}" // This might be wrong, need to check how contactKey
|
||||
// is derived in PagingSource
|
||||
|
||||
// Determine if this is my message (originated on this device)
|
||||
val fromLocal = (data.from == DataPacket.ID_LOCAL || (myId != null && data.from == myId))
|
||||
val toBroadcast = data.to == DataPacket.ID_BROADCAST
|
||||
val fromLocal =
|
||||
(packetData.from == DataPacket.ID_LOCAL || (myId != null && packetData.from == myId))
|
||||
val toBroadcast = packetData.to == DataPacket.ID_BROADCAST
|
||||
|
||||
// grab usernames from NodeInfo
|
||||
val userId = if (fromLocal) data.to else data.from
|
||||
val userId = if (fromLocal) packetData.to else packetData.from
|
||||
val user = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
val node = nodeRepository.getNode(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
val shortName = user.short_name
|
||||
val longName =
|
||||
if (toBroadcast) {
|
||||
channelSet.getChannel(data.channel)?.name ?: "Channel ${data.channel}"
|
||||
channelSet.getChannel(packetData.channel)?.name ?: "Channel ${packetData.channel}"
|
||||
} else {
|
||||
user.long_name
|
||||
}
|
||||
|
||||
val contactKeyComputed =
|
||||
if (toBroadcast) "${packetData.channel}${DataPacket.ID_BROADCAST}" else contactKey
|
||||
|
||||
Contact(
|
||||
contactKey = contactKey,
|
||||
shortName = if (toBroadcast) data.channel.toString() else shortName,
|
||||
contactKey = contactKeyComputed,
|
||||
shortName = if (toBroadcast) packetData.channel.toString() else shortName,
|
||||
longName = longName,
|
||||
lastMessageTime = if (data.time != 0L) data.time else null,
|
||||
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKey),
|
||||
messageCount = packetRepository.getMessageCount(contactKey),
|
||||
isMuted = settings[contactKey]?.isMuted == true,
|
||||
lastMessageTime = if (packetData.time != 0L) packetData.time else null,
|
||||
lastMessageText = if (fromLocal) packetData.text else "$shortName: ${packetData.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKeyComputed),
|
||||
messageCount = packetRepository.getMessageCount(contactKeyComputed),
|
||||
isMuted = settings[contactKeyComputed]?.isMuted == true,
|
||||
isUnmessageable = user.is_unmessagable ?: false,
|
||||
nodeColors =
|
||||
if (!toBroadcast) {
|
||||
|
|
|
|||
|
|
@ -30,17 +30,16 @@ import io.mockk.just
|
|||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.PacketEntity
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
|
|
@ -62,11 +61,8 @@ class SendMessageWorkerTest {
|
|||
fun `doWork returns success when packet is sent successfully`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket("dest", 0, "Hello")
|
||||
val packet = mockk<Packet>(relaxed = true)
|
||||
val packetEntity = PacketEntity(packet = packet)
|
||||
every { packet.data } returns dataPacket
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns packetEntity
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
coEvery { radioController.sendMessage(any()) } just Runs
|
||||
coEvery { packetRepository.updateMessageStatus(any(), any()) } just Runs
|
||||
|
|
@ -99,11 +95,8 @@ class SendMessageWorkerTest {
|
|||
fun `doWork returns retry when radio is disconnected`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket("dest", 0, "Hello")
|
||||
val packet = mockk<Packet>(relaxed = true)
|
||||
val packetEntity = PacketEntity(packet = packet)
|
||||
every { packet.data } returns dataPacket
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns packetEntity
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
val worker =
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.actions
|
||||
import org.meshtastic.core.resources.direct_message
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* 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
|
||||
|
|
@ -14,12 +14,11 @@
|
|||
* 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.feature.node.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
@Composable
|
||||
internal fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.google.maps.android.compose.MapsComposeExperimentalApi
|
|||
import com.google.maps.android.compose.MarkerComposable
|
||||
import com.google.maps.android.compose.rememberCameraPositionState
|
||||
import com.google.maps.android.compose.rememberUpdatedMarkerState
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.ui.component.NodeChip
|
||||
import org.meshtastic.core.ui.component.precisionBitsToMeters
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ import org.meshtastic.core.common.util.bearing
|
|||
import org.meshtastic.core.common.util.latLongToMeter
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.ui.component.precisionBitsToMeters
|
||||
import org.meshtastic.proto.Config
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ import androidx.compose.ui.graphics.Color
|
|||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.administration
|
||||
|
|
@ -41,7 +42,6 @@ import org.meshtastic.core.resources.latest_alpha_firmware
|
|||
import org.meshtastic.core.resources.latest_stable_firmware
|
||||
import org.meshtastic.core.resources.remote_admin
|
||||
import org.meshtastic.core.resources.request_metadata
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.actions
|
||||
import org.meshtastic.core.resources.direct_message
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
import org.meshtastic.core.model.util.UnitConversions.toTempString
|
||||
import org.meshtastic.core.model.util.toSmallDistanceString
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import co.touchlab.kermit.Logger
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.GPSFormat
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.metersIn
|
||||
import org.meshtastic.core.model.util.toString
|
||||
import org.meshtastic.core.resources.Res
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.copy
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.desc_node_filter_clear
|
||||
import org.meshtastic.core.resources.node_filter_exclude_infrastructure
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.resources.Res
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
|
||||
sealed class NodeMenuAction {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.add_a_note
|
||||
import org.meshtastic.core.resources.notes
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.exchange_position
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.channel_1
|
||||
import org.meshtastic.core.resources.channel_2
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.logs
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.details
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
|
||||
import org.meshtastic.feature.node.metrics.EnvironmentMetricsState
|
||||
|
|
|
|||
|
|
@ -16,14 +16,16 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.detail
|
||||
|
||||
import android.os.RemoteException
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.favorite
|
||||
import org.meshtastic.core.resources.favorite_add
|
||||
|
|
@ -37,8 +39,6 @@ import org.meshtastic.core.resources.mute_remove
|
|||
import org.meshtastic.core.resources.remove
|
||||
import org.meshtastic.core.resources.remove_node_text
|
||||
import org.meshtastic.core.resources.unmute
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -49,6 +49,7 @@ class NodeManagementActions
|
|||
constructor(
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val alertManager: AlertManager,
|
||||
) {
|
||||
fun requestRemoveNode(scope: CoroutineScope, node: Node) {
|
||||
|
|
@ -62,13 +63,9 @@ constructor(
|
|||
fun removeNode(scope: CoroutineScope, nodeNum: Int) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Removing node '$nodeNum'" }
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e { "Remove node error: ${ex.message}" }
|
||||
}
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,13 +85,7 @@ constructor(
|
|||
}
|
||||
|
||||
fun ignoreNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e(ex) { "Ignore node error" }
|
||||
}
|
||||
}
|
||||
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Ignore(node)) }
|
||||
}
|
||||
|
||||
fun requestMuteNode(scope: CoroutineScope, node: Node) {
|
||||
|
|
@ -110,13 +101,7 @@ constructor(
|
|||
}
|
||||
|
||||
fun muteNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Mute(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e(ex) { "Mute node error" }
|
||||
}
|
||||
}
|
||||
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Mute(node)) }
|
||||
}
|
||||
|
||||
fun requestFavoriteNode(scope: CoroutineScope, node: Node) {
|
||||
|
|
@ -135,13 +120,7 @@ constructor(
|
|||
}
|
||||
|
||||
fun favoriteNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Favorite(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e(ex) { "Favorite node error" }
|
||||
}
|
||||
}
|
||||
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) }
|
||||
}
|
||||
|
||||
fun setNodeNotes(scope: CoroutineScope, nodeNum: Int, notes: String) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
|
|
@ -44,7 +45,6 @@ import org.meshtastic.core.resources.requesting_from
|
|||
import org.meshtastic.core.resources.signal_quality
|
||||
import org.meshtastic.core.resources.traceroute
|
||||
import org.meshtastic.core.resources.user_info
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ sealed class NodeRequestEffect {
|
|||
}
|
||||
|
||||
@Singleton
|
||||
class NodeRequestActions @Inject constructor(private val serviceRepository: ServiceRepository) {
|
||||
class NodeRequestActions @Inject constructor(private val radioController: RadioController) {
|
||||
|
||||
private val _effects = MutableSharedFlow<NodeRequestEffect>()
|
||||
val effects: SharedFlow<NodeRequestEffect> = _effects.asSharedFlow()
|
||||
|
|
@ -67,34 +67,26 @@ class NodeRequestActions @Inject constructor(private val serviceRepository: Serv
|
|||
fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Requesting UserInfo for '$destNum'" }
|
||||
try {
|
||||
serviceRepository.meshService?.requestUserInfo(destNum)
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName),
|
||||
),
|
||||
)
|
||||
} catch (ex: android.os.RemoteException) {
|
||||
Logger.e { "Request NodeInfo error: ${ex.message}" }
|
||||
}
|
||||
radioController.requestUserInfo(destNum)
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestNeighborInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Requesting NeighborInfo for '$destNum'" }
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName),
|
||||
),
|
||||
)
|
||||
} catch (ex: android.os.RemoteException) {
|
||||
Logger.e { "Request NeighborInfo error: ${ex.message}" }
|
||||
}
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,61 +98,49 @@ class NodeRequestActions @Inject constructor(private val serviceRepository: Serv
|
|||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Requesting position for '$destNum'" }
|
||||
try {
|
||||
serviceRepository.meshService?.requestPosition(destNum, position)
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.position, longName),
|
||||
),
|
||||
)
|
||||
} catch (ex: android.os.RemoteException) {
|
||||
Logger.e { "Request position error: ${ex.message}" }
|
||||
}
|
||||
radioController.requestPosition(destNum, position)
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.position, longName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTelemetry(scope: CoroutineScope, destNum: Int, longName: String, type: TelemetryType) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Requesting telemetry for '$destNum'" }
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
|
||||
val typeRes =
|
||||
when (type) {
|
||||
TelemetryType.DEVICE -> Res.string.request_device_metrics
|
||||
TelemetryType.ENVIRONMENT -> Res.string.request_environment_metrics
|
||||
TelemetryType.AIR_QUALITY -> Res.string.request_air_quality_metrics
|
||||
TelemetryType.POWER -> Res.string.request_power_metrics
|
||||
TelemetryType.LOCAL_STATS -> Res.string.signal_quality
|
||||
TelemetryType.HOST -> Res.string.request_host_metrics
|
||||
TelemetryType.PAX -> Res.string.request_pax_metrics
|
||||
}
|
||||
val typeRes =
|
||||
when (type) {
|
||||
TelemetryType.DEVICE -> Res.string.request_device_metrics
|
||||
TelemetryType.ENVIRONMENT -> Res.string.request_environment_metrics
|
||||
TelemetryType.AIR_QUALITY -> Res.string.request_air_quality_metrics
|
||||
TelemetryType.POWER -> Res.string.request_power_metrics
|
||||
TelemetryType.LOCAL_STATS -> Res.string.signal_quality
|
||||
TelemetryType.HOST -> Res.string.request_host_metrics
|
||||
TelemetryType.PAX -> Res.string.request_pax_metrics
|
||||
}
|
||||
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName)),
|
||||
)
|
||||
} catch (ex: android.os.RemoteException) {
|
||||
Logger.e { "Request telemetry error: ${ex.message}" }
|
||||
}
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTraceroute(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
Logger.i { "Requesting traceroute for '$destNum'" }
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTimes.update { it + (destNum to nowMillis) }
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName),
|
||||
),
|
||||
)
|
||||
} catch (ex: android.os.RemoteException) {
|
||||
Logger.e { "Request traceroute error: ${ex.message}" }
|
||||
}
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTimes.update { it + (destNum to nowMillis) }
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ package org.meshtastic.feature.node.domain.usecase
|
|||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.feature.node.list.NodeFilterState
|
||||
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
|
||||
import org.meshtastic.proto.Config
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.meshtastic.core.data.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
||||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.hasValidEnvironmentMetrics
|
||||
import org.meshtastic.core.model.util.isDirectSignal
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.fallback_node_name
|
||||
|
|
@ -110,7 +110,7 @@ constructor(
|
|||
nodeRepository.myNodeInfo,
|
||||
radioConfigRepository.deviceProfileFlow.onStart { emit(DeviceProfile()) },
|
||||
) { ourNode, myInfo, profile ->
|
||||
IdentityGroup(ourNode, myInfo?.toMyNodeInfo(), profile)
|
||||
IdentityGroup(ourNode, myInfo, profile)
|
||||
}
|
||||
|
||||
// 3. Metadata & Request Timestamps
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* 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
|
||||
|
|
@ -14,12 +14,11 @@
|
|||
* 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.feature.node.list
|
||||
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import javax.inject.Inject
|
||||
|
||||
class NodeFilterPreferences @Inject constructor(private val uiPreferencesDataSource: UiPreferencesDataSource) {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.add_favorite
|
||||
import org.meshtastic.core.resources.channel_invalid
|
||||
|
|
|
|||
|
|
@ -17,11 +17,9 @@
|
|||
package org.meshtastic.feature.node.list
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -30,12 +28,13 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.util.dispatchMeshtasticUri
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
|
||||
|
|
@ -53,6 +52,7 @@ constructor(
|
|||
private val nodeRepository: NodeRepository,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
val nodeManagementActions: NodeManagementActions,
|
||||
private val getFilteredNodesUseCase: GetFilteredNodesUseCase,
|
||||
val nodeFilterPreferences: NodeFilterPreferences,
|
||||
|
|
@ -154,11 +154,7 @@ constructor(
|
|||
radioConfigRepository.replaceAllSettings(channelSet.settings)
|
||||
val newLoraConfig = channelSet.lora_config
|
||||
if (newLoraConfig != null) {
|
||||
try {
|
||||
serviceRepository.meshService?.setConfig(Config(lora = newLoraConfig).encode())
|
||||
} catch (ex: RemoteException) {
|
||||
Logger.e(ex) { "Set config error" }
|
||||
}
|
||||
radioController.setLocalConfig(Config(lora = newLoraConfig))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,20 +46,20 @@ import org.meshtastic.core.common.util.nowSeconds
|
|||
import org.meshtastic.core.common.util.toDate
|
||||
import org.meshtastic.core.common.util.toInstant
|
||||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.model.evaluateTracerouteMapAvailability
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.okay
|
||||
import org.meshtastic.core.resources.traceroute
|
||||
import org.meshtastic.core.resources.view_on_map
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import org.meshtastic.core.ui.util.toMessageRes
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.model
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.isUnmessageableRole
|
||||
|
||||
val Node.isEffectivelyUnmessageable: Boolean
|
||||
get() = user.is_unmessagable ?: (user.role?.isUnmessageableRole() == true)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ package org.meshtastic.feature.node.model
|
|||
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.proto.FirmwareEdition
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
package org.meshtastic.feature.node.model
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.proto.Config
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ import kotlinx.coroutines.test.StandardTestDispatcher
|
|||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import org.meshtastic.proto.User
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ class NodeManagementActionsTest {
|
|||
|
||||
private val nodeRepository = mockk<NodeRepository>(relaxed = true)
|
||||
private val serviceRepository = mockk<ServiceRepository>(relaxed = true)
|
||||
private val radioController = mockk<RadioController>(relaxed = true)
|
||||
private val alertManager = mockk<AlertManager>(relaxed = true)
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
|
|
@ -42,6 +44,7 @@ class NodeManagementActionsTest {
|
|||
NodeManagementActions(
|
||||
nodeRepository = nodeRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
alertManager = alertManager,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.feature.node.list.NodeFilterState
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.proto.User
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.administration
|
||||
import org.meshtastic.core.resources.preserve_favorites
|
||||
|
|
|
|||
|
|
@ -32,11 +32,6 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportDataUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.IsOtaCapableUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.MeshLocationUseCase
|
||||
|
|
@ -45,9 +40,14 @@ import org.meshtastic.core.domain.usecase.settings.SetDatabaseCacheLimitUseCase
|
|||
import org.meshtastic.core.domain.usecase.settings.SetMeshLogSettingsUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.SetProvideLocationUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.repository.DatabaseManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import java.io.BufferedWriter
|
||||
|
|
@ -77,7 +77,7 @@ constructor(
|
|||
private val exportDataUseCase: ExportDataUseCase,
|
||||
private val isOtaCapableUseCase: IsOtaCapableUseCase,
|
||||
) : ViewModel() {
|
||||
val myNodeInfo: StateFlow<MyNodeEntity?> = nodeRepository.myNodeInfo
|
||||
val myNodeInfo: StateFlow<MyNodeInfo?> = nodeRepository.myNodeInfo
|
||||
|
||||
val myNodeNum
|
||||
get() = myNodeInfo.value?.myNodeNum
|
||||
|
|
@ -170,7 +170,7 @@ constructor(
|
|||
*/
|
||||
@Suppress("detekt:CyclomaticComplexMethod", "detekt:LongMethod")
|
||||
fun saveDataCsv(uri: Uri, filterPortnum: Int? = null) {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
viewModelScope.launch {
|
||||
val myNodeNum = myNodeNum ?: return@launch
|
||||
writeToUri(uri) { writer -> exportDataUseCase(writer, myNodeNum, filterPortnum) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ import org.meshtastic.core.common.util.nowInstant
|
|||
import org.meshtastic.core.common.util.toDate
|
||||
import org.meshtastic.core.common.util.toInstant
|
||||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.model.getTracerouteResponse
|
||||
import org.meshtastic.core.model.util.decodeOrNull
|
||||
import org.meshtastic.core.model.util.toReadableString
|
||||
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.debug_clear
|
||||
import org.meshtastic.core.resources.debug_clear_logs_confirm
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.meshtastic.core.prefs.filter.FilterPrefs
|
||||
import org.meshtastic.core.service.filter.MessageFilterService
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -30,7 +30,7 @@ class FilterSettingsViewModel
|
|||
@Inject
|
||||
constructor(
|
||||
private val filterPrefs: FilterPrefs,
|
||||
private val messageFilterService: MessageFilterService,
|
||||
private val messageFilter: MessageFilter,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _filterEnabled = MutableStateFlow(filterPrefs.filterEnabled)
|
||||
|
|
@ -51,7 +51,7 @@ constructor(
|
|||
if (current.add(trimmed)) {
|
||||
filterPrefs.filterWords = current
|
||||
_filterWords.value = current.toList().sorted()
|
||||
messageFilterService.rebuildPatterns()
|
||||
messageFilter.rebuildPatterns()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ constructor(
|
|||
if (current.remove(word)) {
|
||||
filterPrefs.filterWords = current
|
||||
_filterWords.value = current.toList().sorted()
|
||||
messageFilterService.rebuildPatterns()
|
||||
messageFilter.rebuildPatterns()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.clean_node_database_description
|
||||
import org.meshtastic.core.resources.clean_node_database_title
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.settings.CleanNodeDatabaseUseCase
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.are_you_sure
|
||||
import org.meshtastic.core.resources.clean_node_database_confirmation
|
||||
|
|
|
|||
|
|
@ -43,11 +43,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.data.repository.LocationRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.settings.AdminActionsUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportProfileUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportSecurityConfigUseCase
|
||||
|
|
@ -59,15 +54,20 @@ import org.meshtastic.core.domain.usecase.settings.RadioResponseResult
|
|||
import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
|
||||
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
|
||||
import org.meshtastic.core.prefs.map.MapConsentPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.cant_shutdown
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.ui.util.getChannelList
|
||||
import org.meshtastic.feature.settings.navigation.ConfigRoute
|
||||
import org.meshtastic.feature.settings.navigation.ModuleRoute
|
||||
|
|
@ -217,7 +217,7 @@ constructor(
|
|||
Logger.d { "RadioConfigViewModel created" }
|
||||
}
|
||||
|
||||
private val myNodeInfo: StateFlow<MyNodeEntity?>
|
||||
private val myNodeInfo: StateFlow<MyNodeInfo?>
|
||||
get() = nodeRepository.myNodeInfo
|
||||
|
||||
val myNodeNum
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.cancel
|
||||
import org.meshtastic.core.resources.send
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.getColorFrom
|
||||
import org.meshtastic.core.database.model.getStringResFrom
|
||||
import org.meshtastic.core.model.getColorFrom
|
||||
import org.meshtastic.core.model.getStringResFrom
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.tak
|
||||
import org.meshtastic.core.resources.tak_config
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.Capabilities
|
||||
import org.meshtastic.core.model.isUnmessageableRole
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.hardware_model
|
||||
import org.meshtastic.core.resources.licensed_amateur_radio
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ import org.junit.After
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportDataUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.IsOtaCapableUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.MeshLocationUseCase
|
||||
|
|
@ -44,8 +41,13 @@ import org.meshtastic.core.domain.usecase.settings.SetThemeUseCase
|
|||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.repository.DatabaseManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Config(sdk = [34])
|
||||
class SettingsViewModelTest {
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
|
|
@ -58,14 +60,14 @@ class SettingsViewModelTest {
|
|||
private val databaseManager: DatabaseManager = mockk(relaxed = true)
|
||||
private val meshLogPrefs: MeshLogPrefs = mockk(relaxed = true)
|
||||
|
||||
private val setThemeUseCase: SetThemeUseCase = mockk(relaxed = true)
|
||||
private val setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase = mockk(relaxed = true)
|
||||
private val setProvideLocationUseCase: SetProvideLocationUseCase = mockk(relaxed = true)
|
||||
private val setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase = mockk(relaxed = true)
|
||||
private val setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase = mockk(relaxed = true)
|
||||
private val meshLocationUseCase: MeshLocationUseCase = mockk(relaxed = true)
|
||||
private val exportDataUseCase: ExportDataUseCase = mockk(relaxed = true)
|
||||
private val isOtaCapableUseCase: IsOtaCapableUseCase = mockk(relaxed = true)
|
||||
private lateinit var setThemeUseCase: SetThemeUseCase
|
||||
private lateinit var setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase
|
||||
private lateinit var setProvideLocationUseCase: SetProvideLocationUseCase
|
||||
private lateinit var setDatabaseCacheLimitUseCase: SetDatabaseCacheLimitUseCase
|
||||
private lateinit var setMeshLogSettingsUseCase: SetMeshLogSettingsUseCase
|
||||
private lateinit var meshLocationUseCase: MeshLocationUseCase
|
||||
private lateinit var exportDataUseCase: ExportDataUseCase
|
||||
private lateinit var isOtaCapableUseCase: IsOtaCapableUseCase
|
||||
|
||||
private lateinit var viewModel: SettingsViewModel
|
||||
|
||||
|
|
@ -73,6 +75,15 @@ class SettingsViewModelTest {
|
|||
fun setUp() {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
|
||||
setThemeUseCase = mockk(relaxed = true)
|
||||
setAppIntroCompletedUseCase = mockk(relaxed = true)
|
||||
setProvideLocationUseCase = mockk(relaxed = true)
|
||||
setDatabaseCacheLimitUseCase = mockk(relaxed = true)
|
||||
setMeshLogSettingsUseCase = mockk(relaxed = true)
|
||||
meshLocationUseCase = mockk(relaxed = true)
|
||||
exportDataUseCase = mockk(relaxed = true)
|
||||
isOtaCapableUseCase = mockk(relaxed = true)
|
||||
|
||||
// Return real StateFlows to avoid ClassCastException
|
||||
every { databaseManager.cacheLimit } returns MutableStateFlow(100)
|
||||
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.prefs.meshlog.MeshLogPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.prefs.filter.FilterPrefs
|
||||
import org.meshtastic.core.service.filter.MessageFilterService
|
||||
import org.meshtastic.core.repository.MessageFilter
|
||||
|
||||
class FilterSettingsViewModelTest {
|
||||
|
||||
private val filterPrefs: FilterPrefs = mockk(relaxed = true)
|
||||
private val messageFilterService: MessageFilterService = mockk(relaxed = true)
|
||||
private val messageFilter: MessageFilter = mockk(relaxed = true)
|
||||
|
||||
private lateinit var viewModel: FilterSettingsViewModel
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ class FilterSettingsViewModelTest {
|
|||
every { filterPrefs.filterEnabled } returns true
|
||||
every { filterPrefs.filterWords } returns setOf("apple", "banana")
|
||||
|
||||
viewModel = FilterSettingsViewModel(filterPrefs = filterPrefs, messageFilterService = messageFilterService)
|
||||
viewModel = FilterSettingsViewModel(filterPrefs = filterPrefs, messageFilter = messageFilter)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -52,7 +52,7 @@ class FilterSettingsViewModelTest {
|
|||
viewModel.addFilterWord("cherry")
|
||||
|
||||
verify { filterPrefs.filterWords = any() }
|
||||
verify { messageFilterService.rebuildPatterns() }
|
||||
verify { messageFilter.rebuildPatterns() }
|
||||
assertEquals(listOf("apple", "banana", "cherry"), viewModel.filterWords.value)
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class FilterSettingsViewModelTest {
|
|||
viewModel.removeFilterWord("apple")
|
||||
|
||||
verify { filterPrefs.filterWords = any() }
|
||||
verify { messageFilterService.rebuildPatterns() }
|
||||
verify { messageFilter.rebuildPatterns() }
|
||||
assertEquals(listOf("banana"), viewModel.filterWords.value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import org.junit.After
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.settings.CleanNodeDatabaseUseCase
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.data.repository.LocationRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.settings.AdminActionsUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportProfileUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ExportSecurityConfigUseCase
|
||||
|
|
@ -48,10 +44,14 @@ import org.meshtastic.core.domain.usecase.settings.RadioConfigUseCase
|
|||
import org.meshtastic.core.domain.usecase.settings.RadioResponseResult
|
||||
import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
|
||||
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
|
||||
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
|
||||
import org.meshtastic.core.prefs.map.MapConsentPrefs
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.ChannelSettings
|
||||
import org.meshtastic.proto.Config
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue