mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(contact): add manually verified shared contact support (#3283)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
04991dbc5a
commit
24f0417b28
8 changed files with 826 additions and 9 deletions
|
|
@ -918,8 +918,17 @@ class MeshService : Service() {
|
|||
sessionPasskey = a.sessionPasskey
|
||||
}
|
||||
|
||||
private fun handleSharedContactImport(contact: AdminProtos.SharedContact) {
|
||||
handleReceivedUser(contact.nodeNum, contact.user, manuallyVerified = true)
|
||||
}
|
||||
|
||||
// Update our DB of users based on someone sending out a User subpacket
|
||||
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User, channel: Int = 0) {
|
||||
private fun handleReceivedUser(
|
||||
fromNum: Int,
|
||||
p: MeshProtos.User,
|
||||
channel: Int = 0,
|
||||
manuallyVerified: Boolean = false,
|
||||
) {
|
||||
updateNodeInfo(fromNum) {
|
||||
val newNode = (it.isUnknownUser && p.hwModel != MeshProtos.HardwareModel.UNSET)
|
||||
|
||||
|
|
@ -936,6 +945,7 @@ class MeshService : Service() {
|
|||
it.longName = p.longName
|
||||
it.shortName = p.shortName
|
||||
it.channel = channel
|
||||
it.manuallyVerified = manuallyVerified
|
||||
if (newNode) {
|
||||
serviceNotifications.showNewNodeSeenNotification(it)
|
||||
}
|
||||
|
|
@ -1913,14 +1923,33 @@ class MeshService : Service() {
|
|||
is ServiceAction.Favorite -> favoriteNode(action.node)
|
||||
is ServiceAction.Ignore -> ignoreNode(action.node)
|
||||
is ServiceAction.Reaction -> sendReaction(action)
|
||||
is ServiceAction.AddSharedContact -> importContact(action.contact)
|
||||
is ServiceAction.ImportContact -> importContact(action.contact)
|
||||
is ServiceAction.SendContact -> sendContact(action.contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a manually shared contact.
|
||||
*
|
||||
* This function takes a [AdminProtos.SharedContact] proto, marks it as manually verified, sends it for further
|
||||
* processing, and then handles the import specific logic.
|
||||
*
|
||||
* @param contact The [AdminProtos.SharedContact] to be imported.
|
||||
*/
|
||||
private fun importContact(contact: AdminProtos.SharedContact) {
|
||||
val verifiedContact = contact.copy { manuallyVerified = true }
|
||||
sendContact(verifiedContact)
|
||||
handleSharedContactImport(contact = verifiedContact)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a shared contact to the radio via [AdminProtos.AdminMessage]
|
||||
*
|
||||
* @param contact The contact to send.
|
||||
*/
|
||||
private fun sendContact(contact: AdminProtos.SharedContact) {
|
||||
packetHandler.sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { addContact = contact })
|
||||
handleReceivedUser(contact.nodeNum, contact.user)
|
||||
}
|
||||
|
||||
private fun getDeviceMetadata(destNum: Int) = toRemoteExceptions {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.channelSet
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.sharedContact
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -40,12 +41,15 @@ import org.meshtastic.core.data.repository.RadioConfigRepository
|
|||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val VERIFIED_CONTACT_FIRMWARE_CUTOFF = "2.7.12"
|
||||
|
||||
@HiltViewModel
|
||||
class MessageViewModel
|
||||
@Inject
|
||||
|
|
@ -122,6 +126,20 @@ constructor(
|
|||
|
||||
fun getUser(userId: String?) = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
/**
|
||||
* Sends a message to a contact or channel.
|
||||
*
|
||||
* If the message is a direct message (no channel specified), this function will:
|
||||
* - If the device firmware version is older than 2.7.12, it will mark the destination node as a favorite to prevent
|
||||
* it from being removed from the on-device node database.
|
||||
* - If the device firmware version is 2.7.12 or newer, it will send a shared contact to the destination node.
|
||||
*
|
||||
* @param str The message content.
|
||||
* @param contactKey The unique contact key, which is a combination of channel (optional) and node ID. Defaults to
|
||||
* broadcasting on channel 0.
|
||||
* @param replyId The ID of the message this is a reply to, if any.
|
||||
*/
|
||||
@Suppress("NestedBlockDepth")
|
||||
fun sendMessage(str: String, contactKey: String = "0${DataPacket.ID_BROADCAST}", replyId: Int? = null) {
|
||||
// contactKey: unique contact key filter (channel)+(nodeId)
|
||||
val channel = contactKey[0].digitToIntOrNull()
|
||||
|
|
@ -130,9 +148,23 @@ constructor(
|
|||
// if the destination is a node, we need to ensure it's a
|
||||
// favorite so it does not get removed from the on-device node database.
|
||||
if (channel == null) { // no channel specified, so we assume it's a direct message
|
||||
val node = nodeRepository.getNode(dest)
|
||||
if (!node.isFavorite) {
|
||||
favoriteNode(nodeRepository.getNode(dest))
|
||||
val fwVersion = ourNodeInfo.value?.metadata?.firmwareVersion
|
||||
val destNode = nodeRepository.getNode(dest)
|
||||
|
||||
fwVersion?.let { fw ->
|
||||
val ver = DeviceVersion(asString = fw)
|
||||
val verifiedSharedContactsVersion =
|
||||
DeviceVersion(
|
||||
asString = VERIFIED_CONTACT_FIRMWARE_CUTOFF,
|
||||
) // Version cutover to verified shared contacts
|
||||
|
||||
if (ver >= verifiedSharedContactsVersion) {
|
||||
sendSharedContact(destNode)
|
||||
} else {
|
||||
if (!destNode.isFavorite) {
|
||||
favoriteNode(destNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val p = DataPacket(dest, channel ?: 0, str, replyId)
|
||||
|
|
@ -159,6 +191,19 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun sendSharedContact(node: Node) = viewModelScope.launch {
|
||||
try {
|
||||
val contact = sharedContact {
|
||||
nodeNum = node.num
|
||||
user = node.user
|
||||
manuallyVerified = node.manuallyVerified
|
||||
}
|
||||
serviceRepository.onServiceAction(ServiceAction.SendContact(contact = contact))
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e(ex, "Send shared contact error")
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendDataPacket(p: DataPacket) {
|
||||
try {
|
||||
serviceRepository.meshService?.send(p)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue