mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-07 14:16:28 -04:00
Modularize more models/utils (#3182)
This commit is contained in:
@@ -26,7 +26,6 @@ import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.NodeSortOption
|
||||
import com.geeksville.mesh.util.onlineTimeThreshold
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -38,6 +37,7 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NodeInfoDaoTest {
|
||||
@@ -48,129 +48,150 @@ class NodeInfoDaoTest {
|
||||
private val offlineNodeLastHeard = onlineThreshold - 30
|
||||
private val onlineNodeLastHeard = onlineThreshold + 20
|
||||
|
||||
private val unknownNode = NodeEntity(
|
||||
num = 7,
|
||||
user = user {
|
||||
id = "!a1b2c3d4"
|
||||
longName = "Meshtastic c3d4"
|
||||
shortName = "c3d4"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
longName = "Meshtastic c3d4",
|
||||
shortName = null // Dao filter for includeUnknown
|
||||
)
|
||||
|
||||
private val ourNode = NodeEntity(
|
||||
num = 8,
|
||||
user = user {
|
||||
id = "+16508765308".format(8)
|
||||
longName = "Kevin Mester"
|
||||
shortName = "KLO"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
},
|
||||
longName = "Kevin Mester", shortName = "KLO",
|
||||
latitude = 30.267153, longitude = -97.743057, // Austin
|
||||
hopsAway = 0,
|
||||
)
|
||||
|
||||
private val onlineNode = NodeEntity(
|
||||
num = 9,
|
||||
user = user {
|
||||
id = "!25060801"
|
||||
longName = "Meshtastic 0801"
|
||||
shortName = "0801"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0801",
|
||||
shortName = "0801",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard
|
||||
)
|
||||
|
||||
private val offlineNode = NodeEntity(
|
||||
num = 10,
|
||||
user = user {
|
||||
id = "!25060802"
|
||||
longName = "Meshtastic 0802"
|
||||
shortName = "0802"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0802",
|
||||
shortName = "0802",
|
||||
hopsAway = 0,
|
||||
lastHeard = offlineNodeLastHeard
|
||||
)
|
||||
|
||||
private val directNode = NodeEntity(
|
||||
num = 11,
|
||||
user = user {
|
||||
id = "!25060803"
|
||||
longName = "Meshtastic 0803"
|
||||
shortName = "0803"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0803",
|
||||
shortName = "0803",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard
|
||||
)
|
||||
|
||||
private val relayedNode = NodeEntity(
|
||||
num = 12,
|
||||
user = user {
|
||||
id = "!25060804"
|
||||
longName = "Meshtastic 0804"
|
||||
shortName = "0804"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0804",
|
||||
shortName = "0804",
|
||||
hopsAway = 3,
|
||||
lastHeard = onlineNodeLastHeard
|
||||
)
|
||||
|
||||
private val myNodeInfo: MyNodeEntity = MyNodeEntity(
|
||||
myNodeNum = ourNode.num,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
|
||||
private val testPositions = arrayOf(
|
||||
0.0 to 0.0,
|
||||
32.776665 to -96.796989, // Dallas
|
||||
32.960758 to -96.733521, // Richardson
|
||||
32.912901 to -96.781776, // North Dallas
|
||||
29.760427 to -95.369804, // Houston
|
||||
33.748997 to -84.387985, // Atlanta
|
||||
34.052235 to -118.243683, // Los Angeles
|
||||
40.712776 to -74.005974, // New York City
|
||||
41.878113 to -87.629799, // Chicago
|
||||
39.952583 to -75.165222, // Philadelphia
|
||||
)
|
||||
private val testNodes = listOf(ourNode, unknownNode, onlineNode, offlineNode, directNode, relayedNode) + testPositions.mapIndexed { index, pos ->
|
||||
private val unknownNode =
|
||||
NodeEntity(
|
||||
num = 1000 + index,
|
||||
user = user {
|
||||
id = "+165087653%02d".format(9 + index)
|
||||
longName = "Kevin Mester$index"
|
||||
shortName = "KM$index"
|
||||
num = 7,
|
||||
user =
|
||||
user {
|
||||
id = "!a1b2c3d4"
|
||||
longName = "Meshtastic c3d4"
|
||||
shortName = "c3d4"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
longName = "Meshtastic c3d4",
|
||||
shortName = null, // Dao filter for includeUnknown
|
||||
)
|
||||
|
||||
private val ourNode =
|
||||
NodeEntity(
|
||||
num = 8,
|
||||
user =
|
||||
user {
|
||||
id = "+16508765308".format(8)
|
||||
longName = "Kevin Mester"
|
||||
shortName = "KLO"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
|
||||
},
|
||||
longName = "Kevin Mester$index", shortName = "KM$index",
|
||||
latitude = pos.first, longitude = pos.second,
|
||||
lastHeard = 9 + index,
|
||||
longName = "Kevin Mester",
|
||||
shortName = "KLO",
|
||||
latitude = 30.267153,
|
||||
longitude = -97.743057, // Austin
|
||||
hopsAway = 0,
|
||||
)
|
||||
}
|
||||
|
||||
private val onlineNode =
|
||||
NodeEntity(
|
||||
num = 9,
|
||||
user =
|
||||
user {
|
||||
id = "!25060801"
|
||||
longName = "Meshtastic 0801"
|
||||
shortName = "0801"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0801",
|
||||
shortName = "0801",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val offlineNode =
|
||||
NodeEntity(
|
||||
num = 10,
|
||||
user =
|
||||
user {
|
||||
id = "!25060802"
|
||||
longName = "Meshtastic 0802"
|
||||
shortName = "0802"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0802",
|
||||
shortName = "0802",
|
||||
hopsAway = 0,
|
||||
lastHeard = offlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val directNode =
|
||||
NodeEntity(
|
||||
num = 11,
|
||||
user =
|
||||
user {
|
||||
id = "!25060803"
|
||||
longName = "Meshtastic 0803"
|
||||
shortName = "0803"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0803",
|
||||
shortName = "0803",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val relayedNode =
|
||||
NodeEntity(
|
||||
num = 12,
|
||||
user =
|
||||
user {
|
||||
id = "!25060804"
|
||||
longName = "Meshtastic 0804"
|
||||
shortName = "0804"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0804",
|
||||
shortName = "0804",
|
||||
hopsAway = 3,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val myNodeInfo: MyNodeEntity =
|
||||
MyNodeEntity(
|
||||
myNodeNum = ourNode.num,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
|
||||
private val testPositions =
|
||||
arrayOf(
|
||||
0.0 to 0.0,
|
||||
32.776665 to -96.796989, // Dallas
|
||||
32.960758 to -96.733521, // Richardson
|
||||
32.912901 to -96.781776, // North Dallas
|
||||
29.760427 to -95.369804, // Houston
|
||||
33.748997 to -84.387985, // Atlanta
|
||||
34.052235 to -118.243683, // Los Angeles
|
||||
40.712776 to -74.005974, // New York City
|
||||
41.878113 to -87.629799, // Chicago
|
||||
39.952583 to -75.165222, // Philadelphia
|
||||
)
|
||||
private val testNodes =
|
||||
listOf(ourNode, unknownNode, onlineNode, offlineNode, directNode, relayedNode) +
|
||||
testPositions.mapIndexed { index, pos ->
|
||||
NodeEntity(
|
||||
num = 1000 + index,
|
||||
user =
|
||||
user {
|
||||
id = "+165087653%02d".format(9 + index)
|
||||
longName = "Kevin Mester$index"
|
||||
shortName = "KM$index"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
|
||||
},
|
||||
longName = "Kevin Mester$index",
|
||||
shortName = "KM$index",
|
||||
latitude = pos.first,
|
||||
longitude = pos.second,
|
||||
lastHeard = 9 + index,
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun createDb(): Unit = runBlocking {
|
||||
@@ -190,8 +211,8 @@ class NodeInfoDaoTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters.
|
||||
* The list excludes [ourNode] to ensure consistency in the results.
|
||||
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters. The list excludes [ourNode]
|
||||
* to ensure consistency in the results.
|
||||
*/
|
||||
private suspend fun getNodes(
|
||||
sort: NodeSortOption = NodeSortOption.LAST_HEARD,
|
||||
@@ -199,13 +220,17 @@ class NodeInfoDaoTest {
|
||||
includeUnknown: Boolean = true,
|
||||
onlyOnline: Boolean = false,
|
||||
onlyDirect: Boolean = false,
|
||||
) = nodeInfoDao.getNodes(
|
||||
sort = sort.sqlValue,
|
||||
filter = filter,
|
||||
includeUnknown = includeUnknown,
|
||||
hopsAwayMax = if (onlyDirect) 0 else -1,
|
||||
lastHeardMin = if (onlyOnline) onlineTimeThreshold() else -1,
|
||||
).map { list -> list.map { it.toModel() } }.first().filter { it.num != ourNode.num }
|
||||
) = nodeInfoDao
|
||||
.getNodes(
|
||||
sort = sort.sqlValue,
|
||||
filter = filter,
|
||||
includeUnknown = includeUnknown,
|
||||
hopsAwayMax = if (onlyDirect) 0 else -1,
|
||||
lastHeardMin = if (onlyOnline) onlineTimeThreshold() else -1,
|
||||
)
|
||||
.map { list -> list.map { it.toModel() } }
|
||||
.first()
|
||||
.filter { it.num != ourNode.num }
|
||||
|
||||
@Test // node list size
|
||||
fun testNodeListSize() = runBlocking {
|
||||
@@ -237,9 +262,10 @@ class NodeInfoDaoTest {
|
||||
fun testSortByDistance() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.DISTANCE)
|
||||
fun NodeEntity.toNode() = Node(num = num, user = user, position = position)
|
||||
val sortedNodes = nodes.sortedWith( // nodes with invalid (null) positions at the end
|
||||
compareBy<Node> { it.validPosition == null }.thenBy { it.distance(ourNode.toNode()) }
|
||||
)
|
||||
val sortedNodes =
|
||||
nodes.sortedWith( // nodes with invalid (null) positions at the end
|
||||
compareBy<Node> { it.validPosition == null }.thenBy { it.distance(ourNode.toNode()) },
|
||||
)
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@@ -297,9 +323,8 @@ class NodeInfoDaoTest {
|
||||
|
||||
@Test
|
||||
fun testPkcMismatch() = runBlocking {
|
||||
val newNode = testNodes[1].copy(user = testNodes[1].user.copy {
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { 99 })
|
||||
})
|
||||
val newNode =
|
||||
testNodes[1].copy(user = testNodes[1].user.copy { publicKey = ByteString.copyFrom(ByteArray(32) { 99 }) })
|
||||
nodeInfoDao.putAll(listOf(newNode))
|
||||
val nodes = getNodes()
|
||||
val containsMismatchNode = nodes.any { it.mismatchKey }
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PacketDaoTest {
|
||||
@@ -40,25 +41,24 @@ class PacketDaoTest {
|
||||
private lateinit var nodeInfoDao: NodeInfoDao
|
||||
private lateinit var packetDao: PacketDao
|
||||
|
||||
private val myNodeInfo: MyNodeEntity = MyNodeEntity(
|
||||
myNodeNum = 42424242,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
private val myNodeInfo: MyNodeEntity =
|
||||
MyNodeEntity(
|
||||
myNodeNum = 42424242,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
|
||||
private val myNodeNum: Int get() = myNodeInfo.myNodeNum
|
||||
private val myNodeNum: Int
|
||||
get() = myNodeInfo.myNodeNum
|
||||
|
||||
private val testContactKeys = listOf(
|
||||
"0${DataPacket.ID_BROADCAST}",
|
||||
"1!test1234",
|
||||
)
|
||||
private val testContactKeys = listOf("0${DataPacket.ID_BROADCAST}", "1!test1234")
|
||||
|
||||
private fun generateTestPackets(myNodeNum: Int) = testContactKeys.flatMap { contactKey ->
|
||||
List(SAMPLE_SIZE) {
|
||||
@@ -79,14 +79,13 @@ class PacketDaoTest {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
database = Room.inMemoryDatabaseBuilder(context, MeshtasticDatabase::class.java).build()
|
||||
|
||||
nodeInfoDao = database.nodeInfoDao().apply {
|
||||
setMyNodeInfo(myNodeInfo)
|
||||
}
|
||||
nodeInfoDao = database.nodeInfoDao().apply { setMyNodeInfo(myNodeInfo) }
|
||||
|
||||
packetDao = database.packetDao().apply {
|
||||
generateTestPackets(42424243).forEach { insert(it) }
|
||||
generateTestPackets(myNodeNum).forEach { insert(it) }
|
||||
}
|
||||
packetDao =
|
||||
database.packetDao().apply {
|
||||
generateTestPackets(42424243).forEach { insert(it) }
|
||||
generateTestPackets(myNodeNum).forEach { insert(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -21,13 +21,13 @@ import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.message.components.MessageItem
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MessageItemTest {
|
||||
|
||||
@@ -62,7 +62,6 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos.Waypoint
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.gpsDisabled
|
||||
@@ -79,11 +78,12 @@ import com.geeksville.mesh.util.SqlTileWriterExt
|
||||
import com.geeksville.mesh.util.addCopyright
|
||||
import com.geeksville.mesh.util.addScaleBarOverlay
|
||||
import com.geeksville.mesh.util.createLatLongGrid
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import com.geeksville.mesh.waypoint
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer
|
||||
import org.meshtastic.feature.map.model.CustomTileSource
|
||||
|
||||
@@ -84,10 +84,7 @@ import com.geeksville.mesh.ui.metrics.HEADING_DEG
|
||||
import com.geeksville.mesh.ui.metrics.formatPositionTime
|
||||
import com.geeksville.mesh.ui.node.DEG_D
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import com.geeksville.mesh.util.metersIn
|
||||
import com.geeksville.mesh.util.mpsToKmph
|
||||
import com.geeksville.mesh.util.mpsToMph
|
||||
import com.geeksville.mesh.util.toString
|
||||
import com.geeksville.mesh.waypoint
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
@@ -120,6 +117,9 @@ import com.google.maps.android.compose.rememberUpdatedMarkerState
|
||||
import com.google.maps.android.compose.widgets.ScaleBar
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.model.util.mpsToKmph
|
||||
import org.meshtastic.core.model.util.mpsToMph
|
||||
import org.meshtastic.core.strings.R
|
||||
import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
package com.geeksville.mesh;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
parcelable DataPacket;
|
||||
parcelable NodeInfo;
|
||||
parcelable MeshUser;
|
||||
parcelable Position;
|
||||
parcelable MyNodeInfo;
|
||||
import org.meshtastic.core.model.DataPacket;
|
||||
import org.meshtastic.core.model.NodeInfo;
|
||||
import org.meshtastic.core.model.MeshUser;
|
||||
import org.meshtastic.core.model.Position;
|
||||
import org.meshtastic.core.model.MyNodeInfo;
|
||||
|
||||
/**
|
||||
This is the public android API for talking to meshtastic radios.
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.geeksville.mesh.database
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
@@ -26,6 +25,7 @@ import com.geeksville.mesh.android.Logging
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class Converters : Logging {
|
||||
|
||||
@@ -20,7 +20,6 @@ package com.geeksville.mesh.database
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.entity.MetadataEntity
|
||||
@@ -28,7 +27,6 @@ import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.NodeSortOption
|
||||
import com.geeksville.mesh.util.onlineTimeThreshold
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -40,6 +38,8 @@ import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package com.geeksville.mesh.database
|
||||
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.Portnums.PortNum
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
@@ -29,66 +27,52 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import javax.inject.Inject
|
||||
|
||||
class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy<PacketDao>) {
|
||||
private val packetDao by lazy {
|
||||
packetDaoLazy.get()
|
||||
}
|
||||
private val packetDao by lazy { packetDaoLazy.get() }
|
||||
|
||||
fun getWaypoints(): Flow<List<Packet>> = packetDao.getAllPackets(PortNum.WAYPOINT_APP_VALUE)
|
||||
|
||||
fun getContacts(): Flow<Map<String, Packet>> = packetDao.getContactKeys()
|
||||
|
||||
suspend fun getMessageCount(contact: String): Int = withContext(Dispatchers.IO) {
|
||||
packetDao.getMessageCount(contact)
|
||||
}
|
||||
suspend fun getMessageCount(contact: String): Int =
|
||||
withContext(Dispatchers.IO) { packetDao.getMessageCount(contact) }
|
||||
|
||||
suspend fun getUnreadCount(contact: String): Int = withContext(Dispatchers.IO) {
|
||||
packetDao.getUnreadCount(contact)
|
||||
}
|
||||
suspend fun getUnreadCount(contact: String): Int = withContext(Dispatchers.IO) { packetDao.getUnreadCount(contact) }
|
||||
|
||||
suspend fun clearUnreadCount(contact: String, timestamp: Long) = withContext(Dispatchers.IO) {
|
||||
packetDao.clearUnreadCount(contact, timestamp)
|
||||
}
|
||||
suspend fun clearUnreadCount(contact: String, timestamp: Long) =
|
||||
withContext(Dispatchers.IO) { packetDao.clearUnreadCount(contact, timestamp) }
|
||||
|
||||
suspend fun getQueuedPackets(): List<DataPacket>? = withContext(Dispatchers.IO) {
|
||||
packetDao.getQueuedPackets()
|
||||
}
|
||||
suspend fun getQueuedPackets(): List<DataPacket>? = withContext(Dispatchers.IO) { packetDao.getQueuedPackets() }
|
||||
|
||||
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) {
|
||||
packetDao.insert(packet)
|
||||
}
|
||||
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) { packetDao.insert(packet) }
|
||||
|
||||
suspend fun getMessagesFrom(contact: String, getNode: suspend (String?) -> Node) =
|
||||
withContext(Dispatchers.IO) {
|
||||
packetDao.getMessagesFrom(contact).mapLatest { packets ->
|
||||
packets.map { packet ->
|
||||
val message = packet.toMessage(getNode)
|
||||
message.replyId.takeIf { it != null && it != 0 }
|
||||
?.let { getPacketByPacketId(it) }
|
||||
?.toMessage(getNode)
|
||||
?.let { originalMessage -> message.copy(originalMessage = originalMessage) }
|
||||
?: message
|
||||
}
|
||||
suspend fun getMessagesFrom(contact: String, getNode: suspend (String?) -> Node) = withContext(Dispatchers.IO) {
|
||||
packetDao.getMessagesFrom(contact).mapLatest { packets ->
|
||||
packets.map { packet ->
|
||||
val message = packet.toMessage(getNode)
|
||||
message.replyId
|
||||
.takeIf { it != null && it != 0 }
|
||||
?.let { getPacketByPacketId(it) }
|
||||
?.toMessage(getNode)
|
||||
?.let { originalMessage -> message.copy(originalMessage = originalMessage) } ?: message
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus) = withContext(Dispatchers.IO) {
|
||||
packetDao.updateMessageStatus(d, m)
|
||||
}
|
||||
|
||||
suspend fun updateMessageId(d: DataPacket, id: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.updateMessageId(d, id)
|
||||
}
|
||||
suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus) =
|
||||
withContext(Dispatchers.IO) { packetDao.updateMessageStatus(d, m) }
|
||||
|
||||
suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.getPacketById(requestId)
|
||||
}
|
||||
suspend fun updateMessageId(d: DataPacket, id: Int) =
|
||||
withContext(Dispatchers.IO) { packetDao.updateMessageId(d, id) }
|
||||
|
||||
suspend fun getPacketByPacketId(packetId: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.getPacketByPacketId(packetId)
|
||||
}
|
||||
suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) { packetDao.getPacketById(requestId) }
|
||||
|
||||
suspend fun getPacketByPacketId(packetId: Int) =
|
||||
withContext(Dispatchers.IO) { packetDao.getPacketByPacketId(packetId) }
|
||||
|
||||
suspend fun deleteMessages(uuidList: List<Long>) = withContext(Dispatchers.IO) {
|
||||
for (chunk in uuidList.chunked(500)) { // limit number of UUIDs per query
|
||||
@@ -96,37 +80,24 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteContacts(contactList: List<String>) = withContext(Dispatchers.IO) {
|
||||
packetDao.deleteContacts(contactList)
|
||||
}
|
||||
suspend fun deleteContacts(contactList: List<String>) =
|
||||
withContext(Dispatchers.IO) { packetDao.deleteContacts(contactList) }
|
||||
|
||||
suspend fun deleteWaypoint(id: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.deleteWaypoint(id)
|
||||
}
|
||||
suspend fun deleteWaypoint(id: Int) = withContext(Dispatchers.IO) { packetDao.deleteWaypoint(id) }
|
||||
|
||||
suspend fun delete(packet: Packet) = withContext(Dispatchers.IO) {
|
||||
packetDao.delete(packet)
|
||||
}
|
||||
suspend fun delete(packet: Packet) = withContext(Dispatchers.IO) { packetDao.delete(packet) }
|
||||
|
||||
suspend fun update(packet: Packet) = withContext(Dispatchers.IO) {
|
||||
packetDao.update(packet)
|
||||
}
|
||||
suspend fun update(packet: Packet) = withContext(Dispatchers.IO) { packetDao.update(packet) }
|
||||
|
||||
fun getContactSettings(): Flow<Map<String, ContactSettings>> = packetDao.getContactSettings()
|
||||
|
||||
suspend fun getContactSettings(contact: String) = withContext(Dispatchers.IO) {
|
||||
packetDao.getContactSettings(contact) ?: ContactSettings(contact)
|
||||
}
|
||||
suspend fun getContactSettings(contact: String) =
|
||||
withContext(Dispatchers.IO) { packetDao.getContactSettings(contact) ?: ContactSettings(contact) }
|
||||
|
||||
suspend fun setMuteUntil(contacts: List<String>, until: Long) = withContext(Dispatchers.IO) {
|
||||
packetDao.setMuteUntil(contacts, until)
|
||||
}
|
||||
suspend fun setMuteUntil(contacts: List<String>, until: Long) =
|
||||
withContext(Dispatchers.IO) { packetDao.setMuteUntil(contacts, until) }
|
||||
|
||||
suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) {
|
||||
packetDao.insert(reaction)
|
||||
}
|
||||
suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) { packetDao.insert(reaction) }
|
||||
|
||||
suspend fun clearPacketDB() = withContext(Dispatchers.IO) {
|
||||
packetDao.deleteAll()
|
||||
}
|
||||
suspend fun clearPacketDB() = withContext(Dispatchers.IO) { packetDao.deleteAll() }
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.PacketEntity
|
||||
import com.geeksville.mesh.database.entity.ReactionEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
|
||||
@Dao
|
||||
interface PacketDao {
|
||||
@@ -40,7 +40,7 @@ interface PacketDao {
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = :portNum
|
||||
ORDER BY received_time ASC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
fun getAllPackets(portNum: Int): Flow<List<Packet>>
|
||||
|
||||
@@ -50,16 +50,22 @@ interface PacketDao {
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 1
|
||||
ORDER BY received_time DESC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
fun getContactKeys(): Flow<Map<@MapColumn(columnName = "contact_key") String, Packet>>
|
||||
fun getContactKeys(): Flow<
|
||||
Map<
|
||||
@MapColumn(columnName = "contact_key")
|
||||
String,
|
||||
Packet,
|
||||
>,
|
||||
>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT COUNT(*) FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 1 AND contact_key = :contact
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun getMessageCount(contact: String): Int
|
||||
|
||||
@@ -68,7 +74,7 @@ interface PacketDao {
|
||||
SELECT COUNT(*) FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 1 AND contact_key = :contact AND read = 0
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun getUnreadCount(contact: String): Int
|
||||
|
||||
@@ -78,12 +84,11 @@ interface PacketDao {
|
||||
SET read = 1
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 1 AND contact_key = :contact AND read = 0 AND received_time <= :timestamp
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun clearUnreadCount(contact: String, timestamp: Long)
|
||||
|
||||
@Upsert
|
||||
suspend fun insert(packet: Packet)
|
||||
@Upsert suspend fun insert(packet: Packet)
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
@@ -92,7 +97,7 @@ interface PacketDao {
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 1 AND contact_key = :contact
|
||||
ORDER BY received_time DESC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
fun getMessagesFrom(contact: String): Flow<List<PacketEntity>>
|
||||
|
||||
@@ -101,7 +106,7 @@ interface PacketDao {
|
||||
SELECT * FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND data = :data
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun findDataPacket(data: DataPacket): Packet?
|
||||
|
||||
@@ -113,16 +118,16 @@ interface PacketDao {
|
||||
DELETE FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND contact_key IN (:contactList)
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun deleteContacts(contactList: List<String>)
|
||||
|
||||
@Query("DELETE FROM packet WHERE uuid=:uuid")
|
||||
suspend fun _delete(uuid: Long)
|
||||
suspend fun delete(uuid: Long)
|
||||
|
||||
@Transaction
|
||||
suspend fun delete(packet: Packet) {
|
||||
_delete(packet.uuid)
|
||||
delete(packet.uuid)
|
||||
}
|
||||
|
||||
@Query("SELECT packet_id FROM packet WHERE uuid IN (:uuidList)")
|
||||
@@ -140,8 +145,7 @@ interface PacketDao {
|
||||
deletePackets(uuidList)
|
||||
}
|
||||
|
||||
@Update
|
||||
suspend fun update(packet: Packet)
|
||||
@Update suspend fun update(packet: Packet)
|
||||
|
||||
@Transaction
|
||||
suspend fun updateMessageStatus(data: DataPacket, m: MessageStatus) {
|
||||
@@ -160,7 +164,7 @@ interface PacketDao {
|
||||
SELECT data FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
ORDER BY received_time ASC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun getDataPackets(): List<DataPacket>
|
||||
|
||||
@@ -171,7 +175,7 @@ interface PacketDao {
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND packet_id = :requestId
|
||||
ORDER BY received_time DESC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun getPacketById(requestId: Int): Packet?
|
||||
|
||||
@@ -180,8 +184,7 @@ interface PacketDao {
|
||||
suspend fun getPacketByPacketId(packetId: Int): PacketEntity?
|
||||
|
||||
@Transaction
|
||||
suspend fun getQueuedPackets(): List<DataPacket>? =
|
||||
getDataPackets().filter { it.status == MessageStatus.QUEUED }
|
||||
suspend fun getQueuedPackets(): List<DataPacket>? = getDataPackets().filter { it.status == MessageStatus.QUEUED }
|
||||
|
||||
@Query(
|
||||
"""
|
||||
@@ -189,7 +192,7 @@ interface PacketDao {
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND port_num = 8
|
||||
ORDER BY received_time ASC
|
||||
"""
|
||||
""",
|
||||
)
|
||||
suspend fun getAllWaypoints(): List<Packet>
|
||||
|
||||
@@ -200,25 +203,30 @@ interface PacketDao {
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM contact_settings")
|
||||
fun getContactSettings(): Flow<Map<@MapColumn(columnName = "contact_key") String, ContactSettings>>
|
||||
fun getContactSettings(): Flow<
|
||||
Map<
|
||||
@MapColumn(columnName = "contact_key")
|
||||
String,
|
||||
ContactSettings,
|
||||
>,
|
||||
>
|
||||
|
||||
@Query("SELECT * FROM contact_settings WHERE contact_key = :contact")
|
||||
suspend fun getContactSettings(contact: String): ContactSettings?
|
||||
|
||||
@Upsert
|
||||
suspend fun upsertContactSettings(contacts: List<ContactSettings>)
|
||||
@Upsert suspend fun upsertContactSettings(contacts: List<ContactSettings>)
|
||||
|
||||
@Transaction
|
||||
suspend fun setMuteUntil(contacts: List<String>, until: Long) {
|
||||
val contactList = contacts.map { contact ->
|
||||
getContactSettings(contact)?.copy(muteUntil = until)
|
||||
?: ContactSettings(contact_key = contact, muteUntil = until)
|
||||
}
|
||||
val contactList =
|
||||
contacts.map { contact ->
|
||||
getContactSettings(contact)?.copy(muteUntil = until)
|
||||
?: ContactSettings(contact_key = contact, muteUntil = until)
|
||||
}
|
||||
upsertContactSettings(contactList)
|
||||
}
|
||||
|
||||
@Upsert
|
||||
suspend fun insert(reaction: ReactionEntity)
|
||||
@Upsert suspend fun insert(reaction: ReactionEntity)
|
||||
|
||||
@Query("DELETE FROM packet")
|
||||
suspend fun deleteAll()
|
||||
|
||||
@@ -22,7 +22,7 @@ import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = "device_hardware")
|
||||
|
||||
@@ -20,9 +20,9 @@ package com.geeksville.mesh.database.entity
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareRelease
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.NetworkFirmwareRelease
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = "firmware_release")
|
||||
|
||||
@@ -19,12 +19,11 @@ package com.geeksville.mesh.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
|
||||
@Entity(tableName = "my_node")
|
||||
data class MyNodeEntity(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
val myNodeNum: Int,
|
||||
@PrimaryKey(autoGenerate = false) val myNodeNum: Int,
|
||||
val model: String?,
|
||||
val firmwareVersion: String?,
|
||||
val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want
|
||||
@@ -37,7 +36,8 @@ data class MyNodeEntity(
|
||||
val deviceId: String? = "unknown",
|
||||
) {
|
||||
/** A human readable description of the software/hardware version */
|
||||
val firmwareString: String get() = "$model $firmwareVersion"
|
||||
val firmwareString: String
|
||||
get() = "$model $firmwareVersion"
|
||||
|
||||
fun toMyNodeInfo() = MyNodeInfo(
|
||||
myNodeNum = myNodeNum,
|
||||
|
||||
@@ -23,19 +23,19 @@ import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import com.geeksville.mesh.DeviceMetrics
|
||||
import com.geeksville.mesh.EnvironmentMetrics
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.util.onlineTimeThreshold
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.kotlin.isNotEmpty
|
||||
import org.meshtastic.core.model.DeviceMetrics
|
||||
import org.meshtastic.core.model.EnvironmentMetrics
|
||||
import org.meshtastic.core.model.MeshUser
|
||||
import org.meshtastic.core.model.NodeInfo
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
|
||||
data class NodeWithRelations(
|
||||
@Embedded val node: NodeEntity,
|
||||
|
||||
@@ -23,11 +23,11 @@ import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos.User
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.util.getShortDateTime
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.util.getShortDateTime
|
||||
|
||||
data class PacketEntity(
|
||||
@Embedded val packet: Packet,
|
||||
@@ -52,20 +52,15 @@ data class PacketEntity(
|
||||
packetId = packetId,
|
||||
emojis = reactions.toReaction(getNode),
|
||||
replyId = data.replyId,
|
||||
viaMqtt = node.viaMqtt
|
||||
viaMqtt = node.viaMqtt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(
|
||||
tableName = "packet",
|
||||
indices = [
|
||||
Index(value = ["myNodeNum"]),
|
||||
Index(value = ["port_num"]),
|
||||
Index(value = ["contact_key"]),
|
||||
]
|
||||
indices = [Index(value = ["myNodeNum"]), Index(value = ["port_num"]), Index(value = ["contact_key"])],
|
||||
)
|
||||
|
||||
data class Packet(
|
||||
@PrimaryKey(autoGenerate = true) val uuid: Long,
|
||||
@ColumnInfo(name = "myNodeNum", defaultValue = "0") val myNodeNum: Int,
|
||||
@@ -83,26 +78,17 @@ data class Packet(
|
||||
)
|
||||
|
||||
@Entity(tableName = "contact_settings")
|
||||
data class ContactSettings(
|
||||
@PrimaryKey val contact_key: String,
|
||||
val muteUntil: Long = 0L,
|
||||
) {
|
||||
val isMuted get() = System.currentTimeMillis() <= muteUntil
|
||||
data class ContactSettings(@PrimaryKey val contact_key: String, val muteUntil: Long = 0L) {
|
||||
val isMuted
|
||||
get() = System.currentTimeMillis() <= muteUntil
|
||||
}
|
||||
|
||||
data class Reaction(
|
||||
val replyId: Int,
|
||||
val user: User,
|
||||
val emoji: String,
|
||||
val timestamp: Long,
|
||||
)
|
||||
data class Reaction(val replyId: Int, val user: User, val emoji: String, val timestamp: Long)
|
||||
|
||||
@Entity(
|
||||
tableName = "reactions",
|
||||
primaryKeys = ["reply_id", "user_id", "emoji"],
|
||||
indices = [
|
||||
Index(value = ["reply_id"]),
|
||||
],
|
||||
indices = [Index(value = ["reply_id"])],
|
||||
)
|
||||
data class ReactionEntity(
|
||||
@ColumnInfo(name = "reply_id") val replyId: Int,
|
||||
@@ -111,15 +97,8 @@ data class ReactionEntity(
|
||||
val timestamp: Long,
|
||||
)
|
||||
|
||||
private suspend fun ReactionEntity.toReaction(
|
||||
getNode: suspend (userId: String?) -> Node
|
||||
) = Reaction(
|
||||
replyId = replyId,
|
||||
user = getNode(userId).user,
|
||||
emoji = emoji,
|
||||
timestamp = timestamp,
|
||||
)
|
||||
private suspend fun ReactionEntity.toReaction(getNode: suspend (userId: String?) -> Node) =
|
||||
Reaction(replyId = replyId, user = getNode(userId).user, emoji = emoji, timestamp = timestamp)
|
||||
|
||||
private suspend fun List<ReactionEntity>.toReaction(
|
||||
getNode: suspend (userId: String?) -> Node
|
||||
) = this.map { it.toReaction(getNode) }
|
||||
private suspend fun List<ReactionEntity>.toReaction(getNode: suspend (userId: String?) -> Node) =
|
||||
this.map { it.toReaction(getNode) }
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -52,6 +51,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.RecentAddressesDataSource
|
||||
import org.meshtastic.core.datastore.model.RecentAddress
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import org.meshtastic.core.strings.R
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.geeksville.mesh.ui.common.theme.GraphColors.Pink
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Purple
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Red
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Yellow
|
||||
import com.geeksville.mesh.util.UnitConversions
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
enum class Environment(val color: Color) {
|
||||
|
||||
@@ -19,8 +19,8 @@ package com.geeksville.mesh.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.geeksville.mesh.MeshProtos.Routing
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.toRoute
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.Position
|
||||
@@ -59,6 +58,7 @@ import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.prefs.map.MapPrefs
|
||||
|
||||
@@ -26,12 +26,12 @@ import com.geeksville.mesh.TelemetryProtos.DeviceMetrics
|
||||
import com.geeksville.mesh.TelemetryProtos.EnvironmentMetrics
|
||||
import com.geeksville.mesh.TelemetryProtos.PowerMetrics
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.kotlin.isNotEmpty
|
||||
import org.meshtastic.core.model.util.GPSFormat
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.model.util.latLongToMeter
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
data class Node(
|
||||
@@ -114,7 +114,7 @@ data class Node(
|
||||
// @return bearing to the other position in degrees
|
||||
fun bearing(o: Node?): Int? = when {
|
||||
validPosition == null || o?.validPosition == null -> null
|
||||
else -> com.geeksville.mesh.util.bearing(latitude, longitude, o.latitude, o.longitude).toInt()
|
||||
else -> org.meshtastic.core.model.util.bearing(latitude, longitude, o.latitude, o.longitude).toInt()
|
||||
}
|
||||
|
||||
fun gpsString(): String = GPSFormat.toDec(latitude, longitude)
|
||||
|
||||
@@ -32,12 +32,10 @@ import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.channel
|
||||
import com.geeksville.mesh.channelSet
|
||||
@@ -60,7 +58,6 @@ import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.service.ServiceAction
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.util.getShortDate
|
||||
import com.geeksville.mesh.util.safeNumber
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -82,7 +79,10 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.util.getShortDate
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.strings.R
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.app.Application
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareJsonDataSource @Inject constructor(private val application: Application) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.geeksville.mesh.database.entity.DeviceHardwareEntity
|
||||
import com.geeksville.mesh.database.entity.asEntity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareLocalDataSource
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.app.Application
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseJsonDataSource @Inject constructor(private val application: Application) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.geeksville.mesh.database.entity.asEntity
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareRelease
|
||||
import org.meshtastic.core.model.NetworkFirmwareRelease
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseLocalDataSource @Inject constructor(private val firmwareReleaseDaoLazy: Lazy<FirmwareReleaseDao>) {
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
package com.geeksville.mesh.repository.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
|
||||
/**
|
||||
* A snapshot in time of the state of the bluetooth subsystem.
|
||||
*/
|
||||
/** A snapshot in time of the state of the bluetooth subsystem. */
|
||||
data class BluetoothState(
|
||||
/** Whether we have adequate permissions to query bluetooth state */
|
||||
val hasPermissions: Boolean = false,
|
||||
/** If we have adequate permissions and bluetooth is enabled */
|
||||
val enabled: Boolean = false,
|
||||
/** If enabled, a list of the currently bonded devices */
|
||||
val bondedDevices: List<BluetoothDevice> = emptyList()
|
||||
val bondedDevices: List<BluetoothDevice> = emptyList(),
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map { it.anonymize }})"
|
||||
"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map {
|
||||
it.anonymize
|
||||
}})"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.geeksville.mesh.service.BLEConnectionClosing
|
||||
import com.geeksville.mesh.service.BLEException
|
||||
import com.geeksville.mesh.service.RadioNotConnectedException
|
||||
import com.geeksville.mesh.service.SafeBluetooth
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import dagger.assisted.Assisted
|
||||
@@ -37,6 +36,7 @@ import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import java.lang.reflect.Method
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@@ -19,24 +19,22 @@ package com.geeksville.mesh.repository.radio
|
||||
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Bluetooth backend implementation.
|
||||
*/
|
||||
class BluetoothInterfaceSpec @Inject constructor(
|
||||
/** Bluetooth backend implementation. */
|
||||
class BluetoothInterfaceSpec
|
||||
@Inject
|
||||
constructor(
|
||||
private val factory: BluetoothInterfaceFactory,
|
||||
private val bluetoothRepository: BluetoothRepository,
|
||||
) : InterfaceSpec<BluetoothInterface>, Logging {
|
||||
override fun createInterface(rest: String): BluetoothInterface {
|
||||
return factory.create(rest)
|
||||
}
|
||||
) : InterfaceSpec<BluetoothInterface>,
|
||||
Logging {
|
||||
override fun createInterface(rest: String): BluetoothInterface = factory.create(rest)
|
||||
|
||||
/** Return true if this address is still acceptable. For BLE that means, still bonded */
|
||||
override fun addressValid(rest: String): Boolean {
|
||||
val allPaired = bluetoothRepository.state.value.bondedDevices
|
||||
.map { it.address }.toSet()
|
||||
val allPaired = bluetoothRepository.state.value.bondedDevices.map { it.address }.toSet()
|
||||
return if (!allPaired.contains(rest)) {
|
||||
warn("Ignoring stale bond to ${rest.anonymize}")
|
||||
false
|
||||
|
||||
@@ -21,10 +21,8 @@ import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ConfigKt
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.channel
|
||||
@@ -39,6 +37,8 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Position
|
||||
import kotlin.random.Random
|
||||
|
||||
private val defaultLoRaConfig =
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.geeksville.mesh.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
|
||||
import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import com.geeksville.mesh.util.toRemoteExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -48,6 +47,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import org.meshtastic.core.prefs.radio.RadioPrefs
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.DeviceUIProtos
|
||||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
@@ -42,14 +41,9 @@ import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshProtos.FromRadio.PayloadVariantCase
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.StoreAndForwardProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.TelemetryProtos.LocalStats
|
||||
@@ -68,7 +62,6 @@ import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.ReactionEntity
|
||||
import com.geeksville.mesh.fromRadio
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.NO_DEVICE_SELECTED
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.position
|
||||
@@ -78,10 +71,7 @@ import com.geeksville.mesh.repository.network.MQTTRepository
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.telemetry
|
||||
import com.geeksville.mesh.user
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.ignoreException
|
||||
import com.geeksville.mesh.util.toOneLineString
|
||||
import com.geeksville.mesh.util.toPIIString
|
||||
import com.geeksville.mesh.util.toRemoteExceptions
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
@@ -100,7 +90,17 @@ import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.MeshUser
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.NodeInfo
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.getFullTracerouteResponse
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import org.meshtastic.core.model.util.toOneLineString
|
||||
import org.meshtastic.core.model.util.toPIIString
|
||||
import org.meshtastic.core.prefs.mesh.MeshPrefs
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.strings.R
|
||||
@@ -2155,8 +2155,9 @@ class MeshService :
|
||||
toRemoteExceptions {
|
||||
if (p.id == 0) p.id = generatePacketId()
|
||||
|
||||
val bytes = p.bytes!!
|
||||
info(
|
||||
"sendData dest=${p.to}, id=${p.id} <- ${p.bytes!!.size} bytes" +
|
||||
"sendData dest=${p.to}, id=${p.id} <- ${bytes.size} bytes" +
|
||||
" (connectionState=$connectionState)",
|
||||
)
|
||||
|
||||
@@ -2164,7 +2165,7 @@ class MeshService :
|
||||
throw Exception("Port numbers must be non-zero!") // we are now more strict
|
||||
}
|
||||
|
||||
if (p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
|
||||
if (bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
|
||||
p.status = MessageStatus.ERROR
|
||||
throw RemoteException("Message too long")
|
||||
} else {
|
||||
@@ -2188,7 +2189,7 @@ class MeshService :
|
||||
|
||||
GeeksvilleApplication.analytics.track(
|
||||
"data_send",
|
||||
DataPair("num_bytes", p.bytes.size),
|
||||
DataPair("num_bytes", bytes.size),
|
||||
DataPair("type", p.dataType),
|
||||
)
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ package com.geeksville.mesh.service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.NodeInfo
|
||||
|
||||
class MeshServiceBroadcasts(
|
||||
private val context: Context,
|
||||
|
||||
@@ -40,7 +40,7 @@ import com.geeksville.mesh.R.raw
|
||||
import com.geeksville.mesh.TelemetryProtos.LocalStats
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.service.ReplyReceiver.Companion.KEY_TEXT_REPLY
|
||||
import com.geeksville.mesh.util.formatUptime
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
|
||||
package com.geeksville.mesh.service
|
||||
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.BuildUtils.errormsg
|
||||
import com.geeksville.mesh.android.BuildUtils.info
|
||||
@@ -31,8 +29,6 @@ import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import com.geeksville.mesh.fromRadio
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.util.toOneLineString
|
||||
import com.geeksville.mesh.util.toPIIString
|
||||
import dagger.Lazy
|
||||
import java8.util.concurrent.CompletableFuture
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -40,6 +36,10 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.util.toOneLineString
|
||||
import org.meshtastic.core.model.util.toPIIString
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -19,24 +19,22 @@ package com.geeksville.mesh.service
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import androidx.core.app.RemoteInput
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import jakarta.inject.Inject
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
||||
/**
|
||||
* A [BroadcastReceiver] that handles inline replies from notifications.
|
||||
*
|
||||
* This receiver is triggered when a user replies to a message directly from a notification.
|
||||
* It extracts the reply text and the contact key from the intent, sends the message
|
||||
* using the [ServiceRepository], and then cancels the original notification.
|
||||
* This receiver is triggered when a user replies to a message directly from a notification. It extracts the reply text
|
||||
* and the contact key from the intent, sends the message using the [ServiceRepository], and then cancels the original
|
||||
* notification.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class ReplyReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var serviceRepository: ServiceRepository
|
||||
@Inject lateinit var serviceRepository: ServiceRepository
|
||||
|
||||
@Inject
|
||||
lateinit var meshServiceNotifications: MeshServiceNotifications
|
||||
@Inject lateinit var meshServiceNotifications: MeshServiceNotifications
|
||||
|
||||
companion object {
|
||||
const val REPLY_ACTION = "com.geeksville.mesh.REPLY_ACTION"
|
||||
@@ -57,9 +55,7 @@ class ReplyReceiver : BroadcastReceiver() {
|
||||
|
||||
if (remoteInput != null) {
|
||||
val contactKey = intent.getStringExtra(CONTACT_KEY) ?: ""
|
||||
val message = remoteInput.getCharSequence(
|
||||
KEY_TEXT_REPLY
|
||||
)?.toString() ?: ""
|
||||
val message = remoteInput.getCharSequence(KEY_TEXT_REPLY)?.toString() ?: ""
|
||||
sendMessage(message, contactKey)
|
||||
MeshServiceNotifications(context).cancelMessageNotification(contactKey)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ import com.geeksville.mesh.android.AddNavigationTracking
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.setAttributes
|
||||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.channelsGraph
|
||||
@@ -112,6 +111,7 @@ import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.navigation.ConnectionsRoutes
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.geeksville.mesh.ui.common.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.DeviceMetrics.Companion.currentTime
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.deviceMetrics
|
||||
import com.geeksville.mesh.environmentMetrics
|
||||
@@ -28,118 +27,132 @@ import com.geeksville.mesh.paxcount
|
||||
import com.geeksville.mesh.position
|
||||
import com.geeksville.mesh.user
|
||||
import com.google.protobuf.ByteString
|
||||
import org.meshtastic.core.model.DeviceMetrics.Companion.currentTime
|
||||
import kotlin.random.Random
|
||||
|
||||
class NodePreviewParameterProvider : PreviewParameterProvider<Node> {
|
||||
val mickeyMouse = Node(
|
||||
num = 1955,
|
||||
user = user {
|
||||
id = "mickeyMouseId"
|
||||
longName = "Mickey Mouse"
|
||||
shortName = "MM"
|
||||
hwModel = MeshProtos.HardwareModel.TBEAM
|
||||
role = ConfigProtos.Config.DeviceConfig.Role.ROUTER
|
||||
},
|
||||
position = position {
|
||||
latitudeI = 338125110
|
||||
longitudeI = -1179189760
|
||||
altitude = 138
|
||||
satsInView = 4
|
||||
},
|
||||
lastHeard = currentTime(),
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
isFavorite = true,
|
||||
hopsAway = 0
|
||||
)
|
||||
val mickeyMouse =
|
||||
Node(
|
||||
num = 1955,
|
||||
user =
|
||||
user {
|
||||
id = "mickeyMouseId"
|
||||
longName = "Mickey Mouse"
|
||||
shortName = "MM"
|
||||
hwModel = MeshProtos.HardwareModel.TBEAM
|
||||
role = ConfigProtos.Config.DeviceConfig.Role.ROUTER
|
||||
},
|
||||
position =
|
||||
position {
|
||||
latitudeI = 338125110
|
||||
longitudeI = -1179189760
|
||||
altitude = 138
|
||||
satsInView = 4
|
||||
},
|
||||
lastHeard = currentTime(),
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceMetrics =
|
||||
deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
isFavorite = true,
|
||||
hopsAway = 0,
|
||||
)
|
||||
|
||||
val minnieMouse = mickeyMouse.copy(
|
||||
num = Random.nextInt(),
|
||||
user = user {
|
||||
longName = "Minnie Mouse"
|
||||
shortName = "MiMo"
|
||||
id = "minnieMouseId"
|
||||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
},
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
position = position {},
|
||||
hopsAway = 1
|
||||
)
|
||||
val minnieMouse =
|
||||
mickeyMouse.copy(
|
||||
num = Random.nextInt(),
|
||||
user =
|
||||
user {
|
||||
longName = "Minnie Mouse"
|
||||
shortName = "MiMo"
|
||||
id = "minnieMouseId"
|
||||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
},
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
position = position {},
|
||||
hopsAway = 1,
|
||||
)
|
||||
|
||||
private val donaldDuck = Node(
|
||||
num = Random.nextInt(),
|
||||
position = position {
|
||||
latitudeI = 338052347
|
||||
longitudeI = -1179208460
|
||||
altitude = 121
|
||||
satsInView = 66
|
||||
},
|
||||
lastHeard = currentTime() - 300,
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
user = user {
|
||||
id = "donaldDuckId"
|
||||
longName = "Donald Duck, the Grand Duck of the Ducks"
|
||||
shortName = "DoDu"
|
||||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
|
||||
},
|
||||
environmentMetrics = environmentMetrics {
|
||||
temperature = 28.0F
|
||||
relativeHumidity = 50.0F
|
||||
barometricPressure = 1013.25F
|
||||
gasResistance = 0.0F
|
||||
voltage = 3.7F
|
||||
current = 0.0F
|
||||
iaq = 100
|
||||
},
|
||||
paxcounter = paxcount {
|
||||
wifi = 30
|
||||
ble = 39
|
||||
uptime = 420
|
||||
},
|
||||
isFavorite = true,
|
||||
hopsAway = 2
|
||||
)
|
||||
private val donaldDuck =
|
||||
Node(
|
||||
num = Random.nextInt(),
|
||||
position =
|
||||
position {
|
||||
latitudeI = 338052347
|
||||
longitudeI = -1179208460
|
||||
altitude = 121
|
||||
satsInView = 66
|
||||
},
|
||||
lastHeard = currentTime() - 300,
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceMetrics =
|
||||
deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
user =
|
||||
user {
|
||||
id = "donaldDuckId"
|
||||
longName = "Donald Duck, the Grand Duck of the Ducks"
|
||||
shortName = "DoDu"
|
||||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
|
||||
},
|
||||
environmentMetrics =
|
||||
environmentMetrics {
|
||||
temperature = 28.0F
|
||||
relativeHumidity = 50.0F
|
||||
barometricPressure = 1013.25F
|
||||
gasResistance = 0.0F
|
||||
voltage = 3.7F
|
||||
current = 0.0F
|
||||
iaq = 100
|
||||
},
|
||||
paxcounter =
|
||||
paxcount {
|
||||
wifi = 30
|
||||
ble = 39
|
||||
uptime = 420
|
||||
},
|
||||
isFavorite = true,
|
||||
hopsAway = 2,
|
||||
)
|
||||
|
||||
private val unknown = donaldDuck.copy(
|
||||
user = user {
|
||||
id = "myId"
|
||||
longName = "Meshtastic myId"
|
||||
shortName = "myId"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
environmentMetrics = environmentMetrics {},
|
||||
paxcounter = paxcount {},
|
||||
)
|
||||
private val unknown =
|
||||
donaldDuck.copy(
|
||||
user =
|
||||
user {
|
||||
id = "myId"
|
||||
longName = "Meshtastic myId"
|
||||
shortName = "myId"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
environmentMetrics = environmentMetrics {},
|
||||
paxcounter = paxcount {},
|
||||
)
|
||||
|
||||
private val almostNothing = Node(
|
||||
num = Random.nextInt(),
|
||||
)
|
||||
private val almostNothing = Node(num = Random.nextInt())
|
||||
|
||||
override val values: Sequence<Node>
|
||||
get() = sequenceOf(
|
||||
mickeyMouse, // "this" node
|
||||
unknown,
|
||||
almostNothing,
|
||||
minnieMouse,
|
||||
donaldDuck
|
||||
)
|
||||
get() =
|
||||
sequenceOf(
|
||||
mickeyMouse, // "this" node
|
||||
unknown,
|
||||
almostNothing,
|
||||
minnieMouse,
|
||||
donaldDuck,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.model.Node
|
||||
@@ -108,6 +107,7 @@ import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.strings.R
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
@@ -58,6 +57,7 @@ import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -39,8 +39,8 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.ui.common.components.EmojiPickerDialog
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -49,7 +49,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.model.Node
|
||||
@@ -61,6 +60,7 @@ import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.MessageItemColors
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
||||
@@ -48,7 +48,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.EnvironmentMetrics
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
import com.geeksville.mesh.copy
|
||||
@@ -60,7 +59,7 @@ import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -57,7 +57,7 @@ import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.util.formatUptime
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.strings.R
|
||||
import java.text.DecimalFormat
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.util.formatUptime
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.strings.R
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
|
||||
@@ -122,11 +122,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.database.entity.FirmwareRelease
|
||||
import com.geeksville.mesh.database.entity.asDeviceVersion
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.MetricsState
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.Node
|
||||
@@ -148,16 +146,18 @@ import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.geeksville.mesh.util.UnitConversions
|
||||
import com.geeksville.mesh.util.UnitConversions.toTempString
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import com.geeksville.mesh.util.formatUptime
|
||||
import com.geeksville.mesh.util.thenIf
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import com.geeksville.mesh.util.toSmallDistanceString
|
||||
import com.geeksville.mesh.util.toSpeedString
|
||||
import com.mikepenz.markdown.m3.Markdown
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
import org.meshtastic.core.model.util.UnitConversions.toTempString
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.navigation.NodeDetailRoutes
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
|
||||
@@ -46,8 +46,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
@@ -59,6 +57,8 @@ import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.AddContactFAB
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
|
||||
@@ -32,7 +32,7 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
|
||||
@Composable
|
||||
fun LastHeardInfo(modifier: Modifier = Modifier, lastHeard: Int, currentTimeMillis: Long) {
|
||||
|
||||
@@ -43,8 +43,8 @@ import androidx.core.net.toUri
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.HyperlinkBlue
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.util.GPSFormat
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
||||
@@ -25,14 +25,12 @@ import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.database.MeshLogRepository
|
||||
import com.geeksville.mesh.database.NodeRepository
|
||||
import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.geeksville.mesh.util.positionToMeter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -48,6 +46,8 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.util.positionToMeter
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import java.io.BufferedWriter
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.isAnalyticsAvailable
|
||||
@@ -74,6 +73,7 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
|
||||
import org.meshtastic.core.prefs.map.MapConsentPrefs
|
||||
|
||||
@@ -45,7 +45,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,7 +73,6 @@ import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
@@ -82,6 +81,7 @@ import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.common.components.rememberDragDropState
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -41,7 +41,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.ConfigProtos.Config.PositionConfig
|
||||
import com.geeksville.mesh.Position
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.BitwisePreference
|
||||
@@ -53,6 +52,7 @@ import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
|
||||
@@ -30,14 +30,13 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.isUnmessageableRole
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.RegularPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.user
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -54,7 +54,6 @@ import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.BuildUtils.errormsg
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.common.components.CopyIconButton
|
||||
@@ -70,6 +69,7 @@ import com.google.zxing.WriterException
|
||||
import com.journeyapps.barcodescanner.BarcodeEncoder
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
import timber.log.Timber
|
||||
import java.net.MalformedURLException
|
||||
|
||||
@@ -23,6 +23,9 @@ import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.MeshUser
|
||||
import org.meshtastic.core.model.NodeInfo
|
||||
import org.meshtastic.core.model.Position
|
||||
import java.util.Locale
|
||||
|
||||
class NodeInfoTest {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.geeksville.mesh
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.Position
|
||||
|
||||
class PositionTest {
|
||||
@Test
|
||||
@@ -35,5 +36,4 @@ class PositionTest {
|
||||
val position = Position(37.1, 121.1, 35)
|
||||
Assert.assertTrue(position.time != 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
|
||||
package com.geeksville.mesh.model
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
|
||||
class DeviceVersionTest {
|
||||
/** make sure we match the python and device code behavior */
|
||||
@Test
|
||||
fun canParse() {
|
||||
|
||||
assertEquals(10000, DeviceVersion("1.0.0").asInt)
|
||||
assertEquals(10101, DeviceVersion("1.1.1").asInt)
|
||||
assertEquals(12357, DeviceVersion("1.23.57").asInt)
|
||||
assertEquals(12357, DeviceVersion("1.23.57.abde123").asInt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package com.geeksville.mesh.ui.metrics
|
||||
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
|
||||
class EnvironmentMetricsTest {
|
||||
|
||||
|
||||
@@ -19,8 +19,19 @@ plugins {
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.meshtastic.android.library)
|
||||
alias(libs.plugins.meshtastic.kotlinx.serialization)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
}
|
||||
|
||||
android { namespace = "org.meshtastic.core.model" }
|
||||
android {
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
aidl = true
|
||||
}
|
||||
namespace = "org.meshtastic.core.model"
|
||||
}
|
||||
|
||||
dependencies { implementation(projects.core.proto) }
|
||||
dependencies {
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.strings)
|
||||
implementation(libs.timber)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.meshtastic.core.model;
|
||||
|
||||
parcelable DataPacket;
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.meshtastic.core.model;
|
||||
|
||||
parcelable MeshUser;
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.meshtastic.core.model;
|
||||
|
||||
parcelable MyNodeInfo;
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.meshtastic.core.model;
|
||||
|
||||
parcelable NodeInfo;
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.meshtastic.core.model;
|
||||
|
||||
parcelable Position;
|
||||
@@ -15,24 +15,23 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Portnums
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Generic [Parcel.readParcelable] Android 13 compatibility extension.
|
||||
*/
|
||||
private inline fun <reified T : Parcelable> Parcel.readParcelableCompat(loader: ClassLoader?): T? {
|
||||
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
/** Generic [Parcel.readParcelable] Android 13 compatibility extension. */
|
||||
private inline fun <reified T : Parcelable> Parcel.readParcelableCompat(loader: ClassLoader?): T? =
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
readParcelable(loader)
|
||||
} else {
|
||||
readParcelable(loader, T::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
enum class MessageStatus : Parcelable {
|
||||
@@ -41,17 +40,16 @@ enum class MessageStatus : Parcelable {
|
||||
QUEUED, // Waiting to send to the mesh as soon as we connect to the device
|
||||
ENROUTE, // Delivered to the radio, but no ACK or NAK received
|
||||
DELIVERED, // We received an ack
|
||||
ERROR // We received back a nak, message not delivered
|
||||
ERROR, // We received back a nak, message not delivered
|
||||
}
|
||||
|
||||
/**
|
||||
* A parcelable version of the protobuf MeshPacket + Data subpacket.
|
||||
*/
|
||||
/** A parcelable version of the protobuf MeshPacket + Data subpacket. */
|
||||
@Serializable
|
||||
data class DataPacket(
|
||||
var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast
|
||||
val bytes: ByteArray?,
|
||||
val dataType: Int, // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions)
|
||||
// A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions)
|
||||
val dataType: Int,
|
||||
var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost
|
||||
var time: Long = System.currentTimeMillis(), // msecs since 1970
|
||||
var id: Int = 0, // 0 means unassigned
|
||||
@@ -62,62 +60,65 @@ data class DataPacket(
|
||||
var hopStart: Int = 0,
|
||||
var snr: Float = 0f,
|
||||
var rssi: Int = 0,
|
||||
var replyId: Int? = null // If this is a reply to a previous message, this is the ID of that message
|
||||
var replyId: Int? = null, // If this is a reply to a previous message, this is the ID of that message
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* If there was an error with this message, this string describes what was wrong.
|
||||
*/
|
||||
/** If there was an error with this message, this string describes what was wrong. */
|
||||
var errorMessage: String? = null
|
||||
|
||||
/**
|
||||
* Syntactic sugar to make it easy to create text messages
|
||||
*/
|
||||
constructor(to: String?, channel: Int, text: String, replyId: Int? = null) : this(
|
||||
/** Syntactic sugar to make it easy to create text messages */
|
||||
constructor(
|
||||
to: String?,
|
||||
channel: Int,
|
||||
text: String,
|
||||
replyId: Int? = null,
|
||||
) : this(
|
||||
to = to,
|
||||
bytes = text.encodeToByteArray(),
|
||||
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
channel = channel,
|
||||
replyId = replyId ?: 0
|
||||
replyId = replyId ?: 0,
|
||||
)
|
||||
|
||||
/**
|
||||
* If this is a text message, return the string, otherwise null
|
||||
*/
|
||||
/** If this is a text message, return the string, otherwise null */
|
||||
val text: String?
|
||||
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val alert: String?
|
||||
get() = if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
constructor(to: String?, channel: Int, waypoint: MeshProtos.Waypoint) : this(
|
||||
to = to,
|
||||
bytes = waypoint.toByteArray(),
|
||||
dataType = Portnums.PortNum.WAYPOINT_APP_VALUE,
|
||||
channel = channel,
|
||||
)
|
||||
constructor(
|
||||
to: String?,
|
||||
channel: Int,
|
||||
waypoint: MeshProtos.Waypoint,
|
||||
) : this(to = to, bytes = waypoint.toByteArray(), dataType = Portnums.PortNum.WAYPOINT_APP_VALUE, channel = channel)
|
||||
|
||||
val waypoint: MeshProtos.Waypoint?
|
||||
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
|
||||
MeshProtos.Waypoint.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
|
||||
MeshProtos.Waypoint.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val hopsAway: Int
|
||||
get() = if (hopStart == 0 || hopLimit > hopStart) -1 else hopStart - hopLimit
|
||||
|
||||
// Autogenerated comparision, because we have a byte array
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
constructor(
|
||||
parcel: Parcel,
|
||||
) : this(
|
||||
parcel.readString(),
|
||||
parcel.createByteArray(),
|
||||
parcel.readInt(),
|
||||
@@ -131,7 +132,7 @@ data class DataPacket(
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt().let { if (it == 0) null else it }
|
||||
parcel.readInt().let { if (it == 0) null else it },
|
||||
)
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@@ -194,9 +195,7 @@ data class DataPacket(
|
||||
parcel.writeInt(replyId ?: 0)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
// Update our object from our parcel (used for inout parameters
|
||||
fun readFromParcel(parcel: Parcel) {
|
||||
@@ -232,15 +231,12 @@ data class DataPacket(
|
||||
const val PKC_CHANNEL_INDEX = 8
|
||||
|
||||
fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n)
|
||||
fun idToDefaultNodeNum(id: String?): Int? =
|
||||
runCatching { id?.toLong(16)?.toInt() }.getOrNull()
|
||||
|
||||
override fun createFromParcel(parcel: Parcel): DataPacket {
|
||||
return DataPacket(parcel)
|
||||
}
|
||||
@Suppress("MagicNumber")
|
||||
fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull()
|
||||
|
||||
override fun newArray(size: Int): Array<DataPacket?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
override fun createFromParcel(parcel: Parcel): DataPacket = DataPacket(parcel)
|
||||
|
||||
override fun newArray(size: Int): Array<DataPacket?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
@@ -15,37 +15,35 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.model
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Provide structured access to parse and compare device version strings
|
||||
*/
|
||||
data class DeviceVersion(val asString: String) : Comparable<DeviceVersion>, Logging {
|
||||
/** Provide structured access to parse and compare device version strings */
|
||||
data class DeviceVersion(val asString: String) : Comparable<DeviceVersion> {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught", "SwallowedException")
|
||||
val asInt
|
||||
get() = try {
|
||||
verStringToInt(asString)
|
||||
} catch (e: Exception) {
|
||||
warn("Exception while parsing version '$asString', assuming version 0")
|
||||
0
|
||||
}
|
||||
get() =
|
||||
try {
|
||||
verStringToInt(asString)
|
||||
} catch (e: Exception) {
|
||||
Timber.w("Exception while parsing version '$asString', assuming version 0")
|
||||
0
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a version string of the form 1.23.57 to a comparable integer of
|
||||
* the form 12357.
|
||||
* Convert a version string of the form 1.23.57 to a comparable integer of the form 12357.
|
||||
*
|
||||
* Or throw an exception if the string can not be parsed
|
||||
*/
|
||||
@Suppress("TooGenericExceptionThrown", "MagicNumber")
|
||||
private fun verStringToInt(s: String): Int {
|
||||
// Allow 1 to two digits per match
|
||||
val match =
|
||||
Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s)
|
||||
?: throw Exception("Can't parse version $s")
|
||||
val match = Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s) ?: throw Exception("Can't parse version $s")
|
||||
val (major, minor, build) = match.destructured
|
||||
return major.toInt() * 10000 + minor.toInt() * 100 + build.toInt()
|
||||
}
|
||||
|
||||
override fun compareTo(other: DeviceVersion): Int = asInt - other.asInt
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@@ -39,5 +39,6 @@ data class MyNodeInfo(
|
||||
val deviceId: String?,
|
||||
) : Parcelable {
|
||||
/** A human readable description of the software/hardware version */
|
||||
val firmwareString: String get() = "$model $firmwareVersion"
|
||||
val firmwareString: String
|
||||
get() = "$model $firmwareVersion"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.network.model
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.network.model
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -15,15 +15,18 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Parcelable
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.bearing
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.mesh.util.onlineTimeThreshold
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import org.meshtastic.core.model.util.bearing
|
||||
import org.meshtastic.core.model.util.latLongToMeter
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
|
||||
//
|
||||
// model objects that directly map to the corresponding protobufs
|
||||
@@ -74,6 +77,7 @@ data class Position(
|
||||
val precisionBits: Int = 0,
|
||||
) : Parcelable {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
companion object {
|
||||
// / Convert to a double representation of degrees
|
||||
fun degD(i: Int) = i * 1e-7
|
||||
@@ -109,6 +113,7 @@ data class Position(
|
||||
fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude)
|
||||
|
||||
// If GPS gives a crap position don't crash our app
|
||||
@Suppress("MagicNumber")
|
||||
fun isValid(): Boolean = latitude != 0.0 &&
|
||||
longitude != 0.0 &&
|
||||
(latitude >= -90 && latitude <= 90.0) &&
|
||||
@@ -128,6 +133,7 @@ data class DeviceMetrics(
|
||||
val uptimeSeconds: Int,
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
@Suppress("MagicNumber")
|
||||
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
|
||||
}
|
||||
|
||||
@@ -153,6 +159,7 @@ data class EnvironmentMetrics(
|
||||
val lux: Float? = null,
|
||||
val uvLux: Float? = null,
|
||||
) : Parcelable {
|
||||
@Suppress("MagicNumber")
|
||||
companion object {
|
||||
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
|
||||
|
||||
@@ -189,6 +196,7 @@ data class NodeInfo(
|
||||
var hopsAway: Int = 0,
|
||||
) : Parcelable {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
val colors: Pair<Int, Int>
|
||||
get() { // returns foreground and background @ColorInt for each 'num'
|
||||
val r = (num and 0xFF0000) shr 16
|
||||
@@ -204,6 +212,7 @@ data class NodeInfo(
|
||||
val voltage
|
||||
get() = deviceMetrics?.voltage
|
||||
|
||||
@Suppress("ImplicitDefaultLocale")
|
||||
val batteryStr
|
||||
get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else ""
|
||||
|
||||
@@ -234,6 +243,7 @@ data class NodeInfo(
|
||||
}
|
||||
|
||||
// / @return a nice human readable string for the distance, or null for unknown
|
||||
@Suppress("MagicNumber")
|
||||
fun distanceStr(o: NodeInfo?, prefUnits: Int = 0) = distance(o)?.let { dist ->
|
||||
when {
|
||||
dist == 0 -> null // same point
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
@@ -58,7 +58,8 @@ private fun formatUptime(seconds: Long): String {
|
||||
"${hours}h".takeIf { hours > 0 },
|
||||
"${minutes}m".takeIf { minutes > 0 },
|
||||
"${secs}s".takeIf { secs > 0 },
|
||||
).joinToString(" ")
|
||||
)
|
||||
.joinToString(" ")
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@@ -15,12 +15,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import android.widget.EditText
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import org.meshtastic.core.model.BuildConfig
|
||||
|
||||
/**
|
||||
* When printing strings to logs sometimes we want to print useful debugging information about users or positions. But
|
||||
@@ -60,6 +60,7 @@ fun Any.toPIIString() = if (!BuildConfig.DEBUG) {
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
|
||||
val currentTime = (currentTimeMillis / 1000).toInt()
|
||||
val diffMin = (currentTime - lastSeenUnix) / 60
|
||||
@@ -15,10 +15,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
@file:Suppress("MatchingDeclarationName")
|
||||
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.geeksville.mesh.Position
|
||||
import org.meshtastic.core.model.Position
|
||||
import java.util.Locale
|
||||
import kotlin.math.asin
|
||||
import kotlin.math.atan2
|
||||
@@ -52,6 +54,7 @@ fun latLongToMeter(latitudeA: Double, longitudeA: Double, latitudeB: Double, lon
|
||||
}
|
||||
|
||||
// Same as above, but takes Mesh Position proto.
|
||||
@Suppress("MagicNumber")
|
||||
fun positionToMeter(a: Position, b: Position): Double =
|
||||
latLongToMeter(a.latitude * 1e-7, a.longitude * 1e-7, b.latitude * 1e-7, b.longitude * 1e-7)
|
||||
|
||||
@@ -64,6 +67,7 @@ fun positionToMeter(a: Position, b: Position): Double =
|
||||
* @param lon2 Longitude of the second point
|
||||
* @return Bearing between the two points in degrees. A value of 0 means due north.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun bearing(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
|
||||
val lat1Rad = Math.toRadians(lat1)
|
||||
val lon1Rad = Math.toRadians(lon1)
|
||||
@@ -15,16 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import kotlin.math.ln
|
||||
|
||||
object UnitConversions {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun celsiusToFahrenheit(celsius: Float): Float {
|
||||
return (celsius * 1.8F) + 32
|
||||
}
|
||||
fun celsiusToFahrenheit(celsius: Float): Float = (celsius * 1.8F) + 32
|
||||
|
||||
fun Float.toTempString(isFahrenheit: Boolean) = if (isFahrenheit) {
|
||||
val fahrenheit = celsiusToFahrenheit(this)
|
||||
@@ -34,8 +32,8 @@ object UnitConversions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculated the dew point based on the Magnus-Tetens approximation which is a widely used
|
||||
* formula for calculating dew point temperature.
|
||||
* Calculated the dew point based on the Magnus-Tetens approximation which is a widely used formula for calculating
|
||||
* dew point temperature.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun calculateDewPoint(tempCelsius: Float, humidity: Float): Float {
|
||||
@@ -31,6 +31,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.model)
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.coil)
|
||||
"googleImplementation"(libs.bundles.datadog)
|
||||
|
||||
@@ -23,9 +23,9 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.network.BuildConfig
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.meshtastic.core.network
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.meshtastic.core.network
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.network.service.ApiService
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package org.meshtastic.core.network.service
|
||||
|
||||
import de.jensklingenberg.ktorfit.http.GET
|
||||
import org.meshtastic.core.network.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.network.model.NetworkFirmwareReleases
|
||||
import org.meshtastic.core.model.NetworkDeviceHardware
|
||||
import org.meshtastic.core.model.NetworkFirmwareReleases
|
||||
|
||||
interface ApiService {
|
||||
@GET("resource/deviceHardware")
|
||||
|
||||
Reference in New Issue
Block a user