mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Add SFPP confirmed status to Messages and Reactions (#4139)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> Co-authored-by: Mac DeCourcy <github.znq26@slmail.me>
This commit is contained in:
parent
78bd1ad6dd
commit
782c068ead
21 changed files with 2699 additions and 61 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.database.dao
|
||||
|
||||
import androidx.room.Room
|
||||
|
|
@ -24,6 +23,8 @@ import kotlinx.coroutines.flow.first
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
|
@ -31,7 +32,9 @@ import org.junit.runner.RunWith
|
|||
import org.meshtastic.core.database.MeshtasticDatabase
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.ReactionEntity
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.proto.Portnums
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
|
@ -166,6 +169,179 @@ class PacketDaoTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_findPacketsWithId() = runBlocking {
|
||||
val packetId = 12345
|
||||
val packet =
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = "test",
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = true,
|
||||
data = DataPacket(to = DataPacket.ID_BROADCAST, channel = 0, text = "Test").copy(id = packetId),
|
||||
packetId = packetId,
|
||||
)
|
||||
|
||||
packetDao.insert(packet)
|
||||
|
||||
val found = packetDao.findPacketsWithId(packetId)
|
||||
assertEquals(1, found.size)
|
||||
assertEquals(packetId, found[0].packetId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sfppHashPersistence() = runBlocking {
|
||||
val hash = byteArrayOf(1, 2, 3, 4)
|
||||
val packet =
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = "test",
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = true,
|
||||
data = DataPacket(to = DataPacket.ID_BROADCAST, channel = 0, text = "Test"),
|
||||
sfpp_hash = hash,
|
||||
)
|
||||
|
||||
packetDao.insert(packet)
|
||||
|
||||
val retrieved =
|
||||
packetDao.getAllPackets(Portnums.PortNum.TEXT_MESSAGE_APP_VALUE).first().find {
|
||||
it.sfpp_hash?.contentEquals(hash) == true
|
||||
}
|
||||
assertNotNull(retrieved)
|
||||
assertTrue(retrieved?.sfpp_hash?.contentEquals(hash) == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_findPacketBySfppHash() = runBlocking {
|
||||
val hash = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
|
||||
val packet =
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = "test",
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = true,
|
||||
data = DataPacket(to = DataPacket.ID_BROADCAST, channel = 0, text = "Test"),
|
||||
sfpp_hash = hash,
|
||||
)
|
||||
|
||||
packetDao.insert(packet)
|
||||
|
||||
// Exact match
|
||||
val found = packetDao.findPacketBySfppHash(hash)
|
||||
assertNotNull(found)
|
||||
assertTrue(found?.sfpp_hash?.contentEquals(hash) == true)
|
||||
|
||||
// Substring match (first 8 bytes)
|
||||
val shortHash = hash.copyOf(8)
|
||||
val foundShort = packetDao.findPacketBySfppHash(shortHash)
|
||||
assertNotNull(foundShort)
|
||||
assertTrue(foundShort?.sfpp_hash?.contentEquals(hash) == true)
|
||||
|
||||
// No match
|
||||
val wrongHash = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
|
||||
val notFound = packetDao.findPacketBySfppHash(wrongHash)
|
||||
assertNull(notFound)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_findReactionBySfppHash() = runBlocking {
|
||||
val hash = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
|
||||
val reaction =
|
||||
ReactionEntity(
|
||||
myNodeNum = myNodeNum,
|
||||
replyId = 123,
|
||||
userId = "sender",
|
||||
emoji = "👍",
|
||||
timestamp = System.currentTimeMillis(),
|
||||
sfpp_hash = hash,
|
||||
)
|
||||
|
||||
packetDao.insert(reaction)
|
||||
|
||||
val found = packetDao.findReactionBySfppHash(hash)
|
||||
assertNotNull(found)
|
||||
assertTrue(found?.sfpp_hash?.contentEquals(hash) == true)
|
||||
|
||||
val shortHash = hash.copyOf(8)
|
||||
val foundShort = packetDao.findReactionBySfppHash(shortHash)
|
||||
assertNotNull(foundShort)
|
||||
|
||||
val wrongHash = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)
|
||||
assertNull(packetDao.findReactionBySfppHash(wrongHash))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_updateMessageId_persistence() = runBlocking {
|
||||
val initialId = 100
|
||||
val newId = 200
|
||||
val packet =
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = "test",
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = true,
|
||||
data = DataPacket(to = "target", channel = 0, text = "Hello").copy(id = initialId),
|
||||
packetId = initialId,
|
||||
)
|
||||
|
||||
packetDao.insert(packet)
|
||||
|
||||
packetDao.updateMessageId(packet.data, newId)
|
||||
|
||||
val updated = packetDao.getPacketById(newId)
|
||||
assertNotNull(updated)
|
||||
assertEquals(newId, updated?.packetId)
|
||||
assertEquals(newId, updated?.data?.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_updateSFPPStatus_logic() = runBlocking {
|
||||
val packetId = 999
|
||||
val fromNum = 123
|
||||
val toNum = 456
|
||||
val hash = byteArrayOf(9, 8, 7, 6)
|
||||
|
||||
val fromId = DataPacket.nodeNumToDefaultId(fromNum)
|
||||
val toId = DataPacket.nodeNumToDefaultId(toNum)
|
||||
|
||||
val packet =
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = "test",
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = true,
|
||||
data = DataPacket(to = toId, channel = 0, text = "Match me").copy(from = fromId, id = packetId),
|
||||
packetId = packetId,
|
||||
)
|
||||
|
||||
packetDao.insert(packet)
|
||||
|
||||
// Verifying the logic used in PacketRepository
|
||||
val found = packetDao.findPacketsWithId(packetId)
|
||||
found.forEach { p ->
|
||||
if (p.data.from == fromId && p.data.to == toId) {
|
||||
val data = p.data.copy(status = MessageStatus.SFPP_CONFIRMED, sfppHash = hash)
|
||||
packetDao.update(p.copy(data = data, sfpp_hash = hash))
|
||||
}
|
||||
}
|
||||
|
||||
val updated = packetDao.findPacketsWithId(packetId)[0]
|
||||
assertEquals(MessageStatus.SFPP_CONFIRMED, updated.data.status)
|
||||
assertTrue(updated.data.sfppHash?.contentEquals(hash) == true)
|
||||
assertTrue(updated.sfpp_hash?.contentEquals(hash) == true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SAMPLE_SIZE = 10
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.database
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
|
|
@ -23,6 +22,7 @@ import com.google.protobuf.ByteString
|
|||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.PaxcountProtos
|
||||
import org.meshtastic.proto.TelemetryProtos
|
||||
|
|
@ -121,4 +121,9 @@ class Converters {
|
|||
fun bytesToByteString(bytes: ByteArray?): ByteString? = if (bytes == null) null else ByteString.copyFrom(bytes)
|
||||
|
||||
@TypeConverter fun byteStringToBytes(value: ByteString?): ByteArray? = value?.toByteArray()
|
||||
|
||||
@TypeConverter fun messageStatusToInt(value: MessageStatus?): Int = value?.ordinal ?: MessageStatus.UNKNOWN.ordinal
|
||||
|
||||
@TypeConverter
|
||||
fun intToMessageStatus(value: Int): MessageStatus = MessageStatus.entries.getOrElse(value) { MessageStatus.UNKNOWN }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.meshtastic.core.database
|
|||
import android.content.Context
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.DeleteTable
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
|
@ -85,8 +86,10 @@ import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
|
|||
AutoMigration(from = 25, to = 26),
|
||||
AutoMigration(from = 26, to = 27),
|
||||
AutoMigration(from = 27, to = 28),
|
||||
AutoMigration(from = 28, to = 29),
|
||||
AutoMigration(from = 29, to = 30, spec = AutoMigration29to30::class),
|
||||
],
|
||||
version = 28,
|
||||
version = 30,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
@ -115,3 +118,6 @@ abstract class MeshtasticDatabase : RoomDatabase() {
|
|||
|
||||
@DeleteTable.Entries(DeleteTable(tableName = "NodeInfo"), DeleteTable(tableName = "MyNodeInfo"))
|
||||
class AutoMigration12to13 : AutoMigrationSpec
|
||||
|
||||
@DeleteColumn.Entries(DeleteColumn(tableName = "packet", columnName = "reply_id"))
|
||||
class AutoMigration29to30 : AutoMigrationSpec
|
||||
|
|
|
|||
|
|
@ -204,7 +204,13 @@ interface PacketDao {
|
|||
@Query("SELECT packet_id FROM packet WHERE uuid IN (:uuidList)")
|
||||
suspend fun getPacketIdsFrom(uuidList: List<Long>): List<Int>
|
||||
|
||||
@Query("DELETE FROM reactions WHERE reply_id IN (:packetIds)")
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM reactions
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND reply_id IN (:packetIds)
|
||||
""",
|
||||
)
|
||||
suspend fun deleteReactions(packetIds: List<Int>)
|
||||
|
||||
@Transaction
|
||||
|
|
@ -227,7 +233,7 @@ interface PacketDao {
|
|||
@Transaction
|
||||
suspend fun updateMessageId(data: DataPacket, id: Int) {
|
||||
val new = data.copy(id = id)
|
||||
findDataPacket(data)?.let { update(it.copy(data = new)) }
|
||||
findDataPacket(data)?.let { update(it.copy(data = new, packetId = id)) }
|
||||
}
|
||||
|
||||
@Query(
|
||||
|
|
@ -251,9 +257,35 @@ interface PacketDao {
|
|||
suspend fun getPacketById(requestId: Int): Packet?
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM packet WHERE packet_id = :packetId LIMIT 1")
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM packet
|
||||
WHERE packet_id = :packetId
|
||||
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun getPacketByPacketId(packetId: Int): PacketEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM packet
|
||||
WHERE packet_id = :packetId
|
||||
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
""",
|
||||
)
|
||||
suspend fun findPacketsWithId(packetId: Int): List<Packet>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND substr(sfpp_hash, 1, 8) = substr(:hash, 1, 8)
|
||||
""",
|
||||
)
|
||||
suspend fun findPacketBySfppHash(hash: ByteArray): Packet?
|
||||
|
||||
@Transaction
|
||||
suspend fun getQueuedPackets(): List<DataPacket>? = getDataPackets().filter { it.status == MessageStatus.QUEUED }
|
||||
|
||||
|
|
@ -311,9 +343,35 @@ interface PacketDao {
|
|||
|
||||
@Update suspend fun update(reaction: ReactionEntity)
|
||||
|
||||
@Query("SELECT * FROM reactions WHERE packet_id = :packetId LIMIT 1")
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM reactions
|
||||
WHERE packet_id = :packetId
|
||||
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
""",
|
||||
)
|
||||
suspend fun findReactionsWithId(packetId: Int): List<ReactionEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM reactions
|
||||
WHERE packet_id = :packetId
|
||||
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun getReactionByPacketId(packetId: Int): ReactionEntity?
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM reactions
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
|
||||
AND substr(sfpp_hash, 1, 8) = substr(:hash, 1, 8)
|
||||
""",
|
||||
)
|
||||
suspend fun findReactionBySfppHash(hash: ByteArray): ReactionEntity?
|
||||
|
||||
@Transaction
|
||||
suspend fun deleteAll() {
|
||||
deleteAllPackets()
|
||||
|
|
|
|||
|
|
@ -36,11 +36,12 @@ data class PacketEntity(
|
|||
) {
|
||||
suspend fun toMessage(getNode: suspend (userId: String?) -> Node) = with(packet) {
|
||||
val node = getNode(data.from)
|
||||
val isFromLocal = node.user.id == DataPacket.ID_LOCAL || (myNodeNum != 0 && node.num == myNodeNum)
|
||||
Message(
|
||||
uuid = uuid,
|
||||
receivedTime = received_time,
|
||||
node = node,
|
||||
fromLocal = node.user.id == DataPacket.ID_LOCAL,
|
||||
fromLocal = isFromLocal,
|
||||
text = data.text.orEmpty(),
|
||||
time = getShortDateTime(data.time),
|
||||
snr = snr,
|
||||
|
|
@ -50,7 +51,7 @@ data class PacketEntity(
|
|||
status = data.status,
|
||||
routingError = routingError,
|
||||
packetId = packetId,
|
||||
emojis = reactions.toReaction(getNode),
|
||||
emojis = reactions.filter { it.myNodeNum == myNodeNum || it.myNodeNum == 0 }.toReaction(getNode),
|
||||
replyId = data.replyId,
|
||||
viaMqtt = data.viaMqtt,
|
||||
relayNode = data.relayNode,
|
||||
|
|
@ -69,6 +70,7 @@ data class PacketEntity(
|
|||
Index(value = ["port_num"]),
|
||||
Index(value = ["contact_key"]),
|
||||
Index(value = ["contact_key", "port_num", "received_time"]),
|
||||
Index(value = ["packet_id"]),
|
||||
],
|
||||
)
|
||||
data class Packet(
|
||||
|
|
@ -81,10 +83,10 @@ data class Packet(
|
|||
@ColumnInfo(name = "data") val data: DataPacket,
|
||||
@ColumnInfo(name = "packet_id", defaultValue = "0") val packetId: Int = 0,
|
||||
@ColumnInfo(name = "routing_error", defaultValue = "-1") var routingError: Int = -1,
|
||||
@ColumnInfo(name = "reply_id", defaultValue = "0") val replyId: Int = 0,
|
||||
@ColumnInfo(name = "snr", defaultValue = "0") val snr: Float = 0f,
|
||||
@ColumnInfo(name = "rssi", defaultValue = "0") val rssi: Int = 0,
|
||||
@ColumnInfo(name = "hopsAway", defaultValue = "-1") val hopsAway: Int = -1,
|
||||
@ColumnInfo(name = "sfpp_hash") val sfpp_hash: ByteArray? = null,
|
||||
) {
|
||||
companion object {
|
||||
const val RELAY_NODE_SUFFIX_MASK = 0xFF
|
||||
|
|
@ -139,14 +141,17 @@ data class Reaction(
|
|||
val relayNode: Int? = null,
|
||||
val to: String? = null,
|
||||
val channel: Int = 0,
|
||||
val sfppHash: ByteArray? = null,
|
||||
)
|
||||
|
||||
@Suppress("ConstructorParameterNaming")
|
||||
@Entity(
|
||||
tableName = "reactions",
|
||||
primaryKeys = ["reply_id", "user_id", "emoji"],
|
||||
primaryKeys = ["myNodeNum", "reply_id", "user_id", "emoji"],
|
||||
indices = [Index(value = ["reply_id"]), Index(value = ["packet_id"])],
|
||||
)
|
||||
data class ReactionEntity(
|
||||
@ColumnInfo(name = "myNodeNum", defaultValue = "0") val myNodeNum: Int = 0,
|
||||
@ColumnInfo(name = "reply_id") val replyId: Int,
|
||||
@ColumnInfo(name = "user_id") val userId: String,
|
||||
val emoji: String,
|
||||
|
|
@ -162,25 +167,30 @@ data class ReactionEntity(
|
|||
@ColumnInfo(name = "relay_node") val relayNode: Int? = null,
|
||||
@ColumnInfo(name = "to") val to: String? = null,
|
||||
@ColumnInfo(name = "channel", defaultValue = "0") val channel: Int = 0,
|
||||
@ColumnInfo(name = "sfpp_hash") val sfpp_hash: ByteArray? = null,
|
||||
)
|
||||
|
||||
private suspend fun ReactionEntity.toReaction(getNode: suspend (userId: String?) -> Node) = Reaction(
|
||||
replyId = replyId,
|
||||
user = getNode(userId).user,
|
||||
emoji = emoji,
|
||||
timestamp = timestamp,
|
||||
snr = snr,
|
||||
rssi = rssi,
|
||||
hopsAway = hopsAway,
|
||||
packetId = packetId,
|
||||
status = status,
|
||||
routingError = routingError,
|
||||
retryCount = retryCount,
|
||||
relays = relays,
|
||||
relayNode = relayNode,
|
||||
to = to,
|
||||
channel = channel,
|
||||
)
|
||||
private suspend fun ReactionEntity.toReaction(getNode: suspend (userId: String?) -> Node): Reaction {
|
||||
val node = getNode(userId)
|
||||
return Reaction(
|
||||
replyId = replyId,
|
||||
user = node.user,
|
||||
emoji = emoji,
|
||||
timestamp = timestamp,
|
||||
snr = snr,
|
||||
rssi = rssi,
|
||||
hopsAway = hopsAway,
|
||||
packetId = packetId,
|
||||
status = status,
|
||||
routingError = routingError,
|
||||
retryCount = retryCount,
|
||||
relays = relays,
|
||||
relayNode = relayNode,
|
||||
to = to,
|
||||
channel = channel,
|
||||
sfppHash = sfpp_hash,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun List<ReactionEntity>.toReaction(getNode: suspend (userId: String?) -> Node) =
|
||||
this.map { it.toReaction(getNode) }
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import org.meshtastic.core.strings.error
|
|||
import org.meshtastic.core.strings.message_delivery_status
|
||||
import org.meshtastic.core.strings.message_status_enroute
|
||||
import org.meshtastic.core.strings.message_status_queued
|
||||
import org.meshtastic.core.strings.message_status_sfpp_confirmed
|
||||
import org.meshtastic.core.strings.message_status_sfpp_routing
|
||||
import org.meshtastic.core.strings.routing_error_admin_bad_session_key
|
||||
import org.meshtastic.core.strings.routing_error_admin_public_key_unauthorized
|
||||
import org.meshtastic.core.strings.routing_error_bad_request
|
||||
|
|
@ -96,6 +98,8 @@ data class Message(
|
|||
MessageStatus.RECEIVED -> Res.string.delivery_confirmed
|
||||
MessageStatus.QUEUED -> Res.string.message_status_queued
|
||||
MessageStatus.ENROUTE -> Res.string.message_status_enroute
|
||||
MessageStatus.SFPP_ROUTING -> Res.string.message_status_sfpp_routing
|
||||
MessageStatus.SFPP_CONFIRMED -> Res.string.message_status_sfpp_confirmed
|
||||
else -> getStringResFrom(routingError)
|
||||
}
|
||||
return title to text
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue