feat: add emoji reactions to message bubbles (#1421)

* Add tapback emojis to message bubbles

Added TapBackEmojiItem composable to display tapback emojis.
Included it in MessageItem composable for incoming messages.
Added a FlowRow to show tapback emojis below the message bubble.

* feat: Add EmojiPicker View

* feat: show emojis for local messages

* feat: Add emoji tapbacks to messages

This commit introduces the ability to send and receive emoji tapbacks for messages.

- Adds emoji and replyId fields to DataPacket.
- Adds emoji tapback support to the MeshService
- Modifies UIState to handle emojis in message lists.

* feat: store tapbacks in database

Store tapbacks in the database and display them in the message list.
- Add a new table to the database to store tapbacks.
- Add a new DAO method to insert and retrieve tapbacks.
- Update the message list UI to display tapbacks.

* refactor: relation db and other changes

---------

Co-authored-by: Andre K <andrekir@pm.me>
This commit is contained in:
James Rich 2024-12-03 05:57:35 -06:00 committed by GitHub
parent b3f4929cf4
commit 2234f5a713
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1049 additions and 187 deletions

View file

@ -18,10 +18,36 @@
package com.geeksville.mesh.database.entity
import androidx.room.ColumnInfo
import androidx.room.Embedded
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.util.getShortDateTime
data class PacketEntity(
@Embedded val packet: Packet,
@Relation(entity = ReactionEntity::class, parentColumn = "packet_id", entityColumn = "reply_id")
val reactions: List<ReactionEntity> = emptyList(),
) {
suspend fun toMessage(getUser: suspend (userId: String?) -> User) = with(packet) {
Message(
uuid = uuid,
receivedTime = received_time,
user = getUser(data.from),
text = data.text.orEmpty(),
time = getShortDateTime(data.time),
read = read,
status = data.status,
routingError = routingError,
packetId = packetId,
emojis = reactions.toReaction(getUser),
)
}
}
@Entity(
tableName = "packet",
@ -42,6 +68,7 @@ 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,
)
@Entity(tableName = "contact_settings")
@ -51,3 +78,37 @@ data class ContactSettings(
) {
val isMuted get() = System.currentTimeMillis() <= muteUntil
}
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"]),
],
)
data class ReactionEntity(
@ColumnInfo(name = "reply_id") val replyId: Int,
@ColumnInfo(name = "user_id") val userId: String,
val emoji: String,
val timestamp: Long,
)
private suspend fun ReactionEntity.toReaction(
getUser: suspend (userId: String?) -> User
) = Reaction(
replyId = replyId,
user = getUser(userId),
emoji = emoji,
timestamp = timestamp,
)
private suspend fun List<ReactionEntity>.toReaction(
getUser: suspend (userId: String?) -> User
) = this.map { it.toReaction(getUser) }