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

Reaction codec libxmtp integration #343

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import org.xmtp.android.library.codecs.ReactionAction
import org.xmtp.android.library.codecs.ReactionCodec
import org.xmtp.android.library.codecs.ReactionSchema
import org.xmtp.android.library.messages.walletAddress
import uniffi.xmtpv3.FfiReaction
import uniffi.xmtpv3.FfiReactionAction
import uniffi.xmtpv3.FfiReactionSchema
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeReactionV2
import uniffi.xmtpv3.org.xmtp.android.library.codecs.ReactionV2Codec

@RunWith(AndroidJUnit4::class)
class ReactionTest {
Expand Down Expand Up @@ -98,4 +103,83 @@ class ReactionTest {
assertEquals(ReactionSchema.Unicode, content?.schema)
}
}

@Test
fun testReactionV2CodecEncodeDecode() {
val codec = ReactionV2Codec()

// Create a sample FfiReaction object
val originalReaction = FfiReaction(
reference = "message123",
referenceInboxId = "inbox456",
action = FfiReactionAction.ADDED,
content = "U+1F603",
schema = FfiReactionSchema.UNICODE
)

// Encode the reaction
val encodedContent = codec.encode(originalReaction)

// Decode the encoded content back to a FfiReaction object
val decodedReaction = codec.decode(encodedContent)

// Assert that the original and decoded reactions are equal
assertEquals(originalReaction.reference, decodedReaction.reference)
assertEquals(originalReaction.referenceInboxId, decodedReaction.referenceInboxId)
assertEquals(originalReaction.action, decodedReaction.action)
assertEquals(originalReaction.content, decodedReaction.content)
assertEquals(originalReaction.schema, decodedReaction.schema)
}

@Test
fun testCanUseReactionV2Codec() {
Client.register(codec = ReactionV2Codec())

val fixtures = fixtures()
val aliceClient = fixtures.alixClient
val membersToInvite = listOf(fixtures.bo.walletAddress)
val aliceConversation = runBlocking {
aliceClient.conversations.newGroup(membersToInvite)
}

runBlocking { aliceConversation.send(text = "hey alice 2 bob") }

val messageToReact = runBlocking { aliceConversation.messages()[0] }

val reaction = FfiReaction(
reference = messageToReact.id,
referenceInboxId = aliceClient.inboxId,
action = FfiReactionAction.ADDED,
content = "U+1F603",
schema = FfiReactionSchema.UNICODE,

)

runBlocking {
aliceConversation.send(
content = reaction,
options = SendOptions(contentType = ContentTypeReactionV2),
)
aliceConversation.sync()
}

val messages = runBlocking { aliceConversation.messages() }
assertEquals(messages.size, 3)
if (messages.size == 3) {
val content: FfiReaction? = messages.first().content()
assertEquals("U+1F603", content?.content)
assertEquals(messageToReact.id, content?.reference)
assertEquals(FfiReactionAction.ADDED, content?.action)
assertEquals(FfiReactionSchema.UNICODE, content?.schema)
}
val messagesWithReactions = runBlocking { aliceConversation.messagesWithReactions()}
assertEquals(messagesWithReactions.size, 2)
if (messagesWithReactions.size == 2) {
val content: FfiReaction? = messagesWithReactions.get(0).childMessages.first().content()
assertEquals("U+1F603", content?.content)
assertEquals(messageToReact.id, content?.reference)
// assertEquals(FfiReactionAction.ADDED, content?.action)
// assertEquals(FfiReactionSchema.UNICODE, content?.schema)
}
}
}
6 changes: 3 additions & 3 deletions library/src/main/java/libxmtp-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Version: 964a227c
Branch: main
Date: 2024-11-26 17:49:43 +0000
Version: 863a997a
Branch: cv/reaction-libxmtp-integration
Date: 2024-12-03 12:06:16 +0000
13 changes: 13 additions & 0 deletions library/src/main/java/org/xmtp/android/library/Conversation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ sealed class Conversation {
}
}

suspend fun messagesWithReactions(
limit: Int? = null,
beforeNs: Long? = null,
afterNs: Long? = null,
direction: Message.SortDirection = Message.SortDirection.DESCENDING,
deliveryStatus: Message.MessageDeliveryStatus = Message.MessageDeliveryStatus.ALL,
): List<DecodedMessageWithChildMessages> {
return when (this) {
is Group -> group.messagesWithReactions(limit, beforeNs, afterNs, direction, deliveryStatus)
is Dm -> throw NotImplementedError("messagesWithReactions not implemented for DMs")
}
}

suspend fun processMessage(messageBytes: ByteArray): Message {
return when (this) {
is Group -> group.processMessage(messageBytes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,26 @@ data class DecodedMessage(
return content() as String? ?: fallbackContent
}
}

data class DecodedMessageWithChildMessages(
var id: String = "",
val client: Client,
var topic: String,
var encodedContent: Content.EncodedContent,
var senderAddress: String,
var sent: Date,
var sentNs: Long,
var deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED,
var childMessages: List<DecodedMessage>
) {
fun <T> content(): T? =
encodedContent.decoded()

val fallbackContent: String
get() = encodedContent.fallback

val body: String
get() {
return content() as String? ?: fallbackContent
}
}
45 changes: 45 additions & 0 deletions library/src/main/java/org/xmtp/android/library/Group.kt
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,51 @@ class Group(val client: Client, private val libXMTPGroup: FfiConversation) {
}
}

suspend fun messagesWithReactions(
limit: Int? = null,
beforeNs: Long? = null,
afterNs: Long? = null,
direction: SortDirection = SortDirection.DESCENDING,
deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.ALL,
): List<DecodedMessageWithChildMessages> {
val reactions = libXMTPGroup.findMessagesWithReactions(
opts = FfiListMessagesOptions(
sentBeforeNs = beforeNs,
sentAfterNs = afterNs,
limit = limit?.toLong(),
deliveryStatus = when (deliveryStatus) {
MessageDeliveryStatus.PUBLISHED -> FfiDeliveryStatus.PUBLISHED
MessageDeliveryStatus.UNPUBLISHED -> FfiDeliveryStatus.UNPUBLISHED
MessageDeliveryStatus.FAILED -> FfiDeliveryStatus.FAILED
else -> null
},
direction = when (direction) {
SortDirection.ASCENDING -> FfiDirection.ASCENDING
else -> FfiDirection.DESCENDING
}
)
)

return reactions.mapNotNull { messageWithReactions ->
val parentMessage = Message(client, messageWithReactions.message).decodeOrNull() ?: return@mapNotNull null
val childMessages = messageWithReactions.reactions.mapNotNull { childMessage ->
Message(client, childMessage).decodeOrNull()
}

DecodedMessageWithChildMessages(
id = parentMessage.id,
client = parentMessage.client,
topic = parentMessage.topic,
encodedContent = parentMessage.encodedContent,
senderAddress = parentMessage.senderAddress,
sent = parentMessage.sent,
sentNs = parentMessage.sentNs,
deliveryStatus = parentMessage.deliveryStatus,
childMessages = childMessages
)
}
}

suspend fun processMessage(messageBytes: ByteArray): Message {
val message = libXMTPGroup.processStreamedConversationMessage(messageBytes)
return Message(client, message)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package uniffi.xmtpv3.org.xmtp.android.library.codecs

import com.google.protobuf.kotlin.toByteString
import org.xmtp.android.library.codecs.ContentCodec
import org.xmtp.android.library.codecs.ContentTypeId
import org.xmtp.android.library.codecs.ContentTypeIdBuilder
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.codecs.ReactionAction
import org.xmtp.android.library.codecs.ReactionSchema
import uniffi.xmtpv3.FfiReaction
import uniffi.xmtpv3.FfiReactionAction
import uniffi.xmtpv3.FfiReactionSchema
import uniffi.xmtpv3.decodeReaction
import uniffi.xmtpv3.encodeReaction

val ContentTypeReactionV2 = ContentTypeIdBuilder.builderFromAuthorityId(
"xmtp.org",
"reaction",
versionMajor = 2,
versionMinor = 0,
)

data class ReactionV2Codec(override var contentType: ContentTypeId = ContentTypeReactionV2) :
ContentCodec<FfiReaction> {

override fun encode(content: FfiReaction): EncodedContent {
return EncodedContent.parseFrom(encodeReaction(content))
}

override fun decode(content: EncodedContent): FfiReaction {
return decodeReaction(content.toByteArray())
}

override fun fallback(content: FfiReaction): String? {
return when (content.action) {
FfiReactionAction.ADDED -> "Reacted “${content.content}” to an earlier message"
FfiReactionAction.REMOVED -> "Removed “${content.content}” from an earlier message"
else -> null
}
}

override fun shouldPush(content: FfiReaction): Boolean = when (content.action) {
FfiReactionAction.ADDED -> true
else -> false
}
}
Loading
Loading