Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add messages Reply functionality, improve Reaction ux #1466

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
572 changes: 572 additions & 0 deletions app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/16.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.database.entity.ReactionEntity
import com.geeksville.mesh.database.entity.ReplyEntity

@Database(
entities = [
Expand All @@ -46,6 +47,7 @@ import com.geeksville.mesh.database.entity.ReactionEntity
MeshLog::class,
QuickChatAction::class,
ReactionEntity::class,
ReplyEntity::class,
],
autoMigrations = [
AutoMigration(from = 3, to = 4),
Expand All @@ -60,8 +62,9 @@ import com.geeksville.mesh.database.entity.ReactionEntity
AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class),
AutoMigration(from = 13, to = 14),
AutoMigration(from = 14, to = 15),
AutoMigration(from = 15, to = 16),
],
version = 15,
version = 16,
exportSchema = true,
)
@TypeConverters(Converters::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.geeksville.mesh.database.dao.PacketDao
import com.geeksville.mesh.database.entity.ContactSettings
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.ReactionEntity
import com.geeksville.mesh.database.entity.ReplyEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -107,4 +108,8 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) {
packetDao.insert(reaction)
}

suspend fun insertReply(reply: ReplyEntity) = withContext(Dispatchers.IO) {
packetDao.insert(reply)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.geeksville.mesh.database.entity.ContactSettings
import com.geeksville.mesh.database.entity.PacketEntity
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.ReactionEntity
import com.geeksville.mesh.database.entity.ReplyEntity
import kotlinx.coroutines.flow.Flow

@Dao
Expand Down Expand Up @@ -214,4 +215,7 @@ interface PacketDao {

@Upsert
suspend fun insert(reaction: ReactionEntity)

@Upsert
suspend fun insert(reply: ReplyEntity)
}
37 changes: 37 additions & 0 deletions app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ data class PacketEntity(
@Embedded val packet: Packet,
@Relation(entity = ReactionEntity::class, parentColumn = "packet_id", entityColumn = "reply_id")
val reactions: List<ReactionEntity> = emptyList(),
@Relation(entity = ReplyEntity::class, parentColumn = "packet_id", entityColumn = "reply_id")
val replies: List<ReplyEntity> = emptyList(),
) {
suspend fun toMessage(getNode: suspend (userId: String?) -> NodeEntity) = with(packet) {
Message(
Expand All @@ -45,6 +47,7 @@ data class PacketEntity(
routingError = routingError,
packetId = packetId,
emojis = reactions.toReaction(getNode),
replies = replies.toReply(getNode),
)
}
}
Expand Down Expand Up @@ -112,3 +115,37 @@ private suspend fun ReactionEntity.toReaction(
private suspend fun List<ReactionEntity>.toReaction(
getNode: suspend (userId: String?) -> NodeEntity
) = this.map { it.toReaction(getNode) }

data class Reply(
val replyId: Int,
val user: User,
val message: String,
val timestamp: Long,
)

@Entity(
tableName = "replies",
primaryKeys = ["reply_id", "user_id", "message"],
indices = [
Index(value = ["reply_id"]),
],
)
data class ReplyEntity(
@ColumnInfo(name = "reply_id") val replyId: Int,
@ColumnInfo(name = "user_id") val userId: String,
val message: String,
val timestamp: Long,
)

private suspend fun ReplyEntity.toReply(
getNode: suspend (userId: String?) -> NodeEntity
) = Reply(
replyId = replyId,
user = getNode(userId).user,
message = message,
timestamp = timestamp,
)

private suspend fun List<ReplyEntity>.toReply(
getNode: suspend (userId: String?) -> NodeEntity
) = this.map { it.toReply(getNode) }
2 changes: 2 additions & 0 deletions app/src/main/java/com/geeksville/mesh/model/Message.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.database.entity.Reaction
import com.geeksville.mesh.database.entity.Reply

val Routing.Error.stringRes: Int
get() = when (this) {
Expand Down Expand Up @@ -55,6 +56,7 @@ data class Message(
val routingError: Int,
val packetId: Int,
val emojis: List<Reaction>,
val replies: List<Reply>,
) {
private fun getStatusStringRes(value: Int): Int {
val error = Routing.Error.forNumber(value) ?: Routing.Error.UNRECOGNIZED
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/geeksville/mesh/model/UIState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ class UIViewModel @Inject constructor(
radioConfigRepository.onServiceAction(ServiceAction.Reaction(emoji, replyId, contactKey))
}

fun sendReply(message: String, replyId: Int, contactKey: String) = viewModelScope.launch {
radioConfigRepository.onServiceAction(ServiceAction.Reply(message, replyId, contactKey))
}

fun requestTraceroute(destNum: Int) {
info("Requesting traceroute for '$destNum'")
try {
Expand Down
64 changes: 62 additions & 2 deletions app/src/main/java/com/geeksville/mesh/service/MeshService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,62 @@ import android.os.IBinder
import android.os.RemoteException
import androidx.core.app.ServiceCompat
import androidx.core.location.LocationCompat
import com.geeksville.mesh.*
import com.geeksville.mesh.AdminProtos
import com.geeksville.mesh.AppOnlyProtos
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.IMeshService
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
import com.geeksville.mesh.MeshProtos
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.R
import com.geeksville.mesh.StoreAndForwardProtos
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.TelemetryProtos.LocalStats
import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.config
import com.geeksville.mesh.copy
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.MeshLog
import com.geeksville.mesh.database.entity.MyNodeEntity
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.database.entity.ReplyEntity
import com.geeksville.mesh.database.entity.toNodeInfo
import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.getTracerouteResponse
import com.geeksville.mesh.position
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
import com.geeksville.mesh.repository.network.MQTTRepository
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
import com.geeksville.mesh.util.*
import com.geeksville.mesh.telemetry
import com.geeksville.mesh.user
import com.geeksville.mesh.util.anonymize
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
import dagger.Lazy
Expand All @@ -79,6 +108,7 @@ import kotlin.math.absoluteValue
sealed class ServiceAction {
data class Ignore(val node: NodeEntity) : ServiceAction()
data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction()
data class Reply(val message: String, val replyId: Int, val contactKey: String) : ServiceAction()
}

/**
Expand Down Expand Up @@ -306,6 +336,7 @@ class MeshService : Service(), Logging {
when (action) {
is ServiceAction.Ignore -> ignoreNode(action.node)
is ServiceAction.Reaction -> sendReaction(action)
is ServiceAction.Reply -> sendReply(action)
}
}.launchIn(serviceScope)

Expand Down Expand Up @@ -643,6 +674,16 @@ class MeshService : Service(), Logging {
packetRepository.get().insertReaction(reaction)
}

private fun rememberReply(packet: MeshPacket) = serviceScope.handledLaunch {
val reply = ReplyEntity(
replyId = packet.decoded.replyId,
userId = toNodeID(packet.from),
message = packet.decoded.payload.toByteArray().decodeToString(),
timestamp = System.currentTimeMillis(),
)
packetRepository.get().insertReply(reply)
}

private fun rememberDataPacket(dataPacket: DataPacket, updateNotification: Boolean = true) {
if (dataPacket.dataType !in rememberDataType) return
val fromLocal = dataPacket.from == DataPacket.ID_LOCAL
Expand Down Expand Up @@ -698,6 +739,9 @@ class MeshService : Service(), Logging {
if (data.emoji != 0) {
debug("Received EMOJI from $fromId")
rememberReaction(packet)
} else if (data.replyId != 0) {
debug("Received REPLY from $fromId")
rememberReply(packet)
} else {
debug("Received CLEAR_TEXT from $fromId")
rememberDataPacket(dataPacket)
Expand Down Expand Up @@ -1793,6 +1837,22 @@ class MeshService : Service(), Logging {
rememberReaction(packet.copy { from = myNodeNum })
}

private fun sendReply(reply: ServiceAction.Reply) = toRemoteExceptions {
val channel = reply.contactKey[0].digitToInt()
val destNum = reply.contactKey.substring(1)

val packet = newMeshPacketTo(destNum).buildMeshPacket(
channel = channel,
priority = MeshPacket.Priority.DEFAULT,
) {
replyId = reply.replyId
portnumValue = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE
payload = ByteString.copyFrom(reply.message.encodeToByteArray())
}
sendToRadio(packet)
rememberReply(packet.copy { from = myNodeNum })
}

private val binder = object : IMeshService.Stub() {

override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
Expand Down
Loading
Loading