Skip to content

Commit 3d4280e

Browse files
committed
Merge branch 'main' of github.com:xmtp/xmtp-android into st/group-preference-actions
2 parents c91ca73 + 9f81f85 commit 3d4280e

File tree

10 files changed

+310
-22
lines changed

10 files changed

+310
-22
lines changed

library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt

+64
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ class GroupTest {
7979
runBlocking { alixGroup.sync() }
8080
assertEquals(alixGroup.memberAddresses().size, 3)
8181
assertEquals(boGroup.memberAddresses().size, 3)
82+
83+
assertEquals(boGroup.permissionLevel(), GroupPermissions.EVERYONE_IS_ADMIN)
84+
assertEquals(alixGroup.permissionLevel(), GroupPermissions.EVERYONE_IS_ADMIN)
85+
assertEquals(boGroup.adminAddress().lowercase(), boClient.address.lowercase())
86+
assertEquals(alixGroup.adminAddress().lowercase(), boClient.address.lowercase())
87+
assert(boGroup.isAdmin())
88+
assert(!alixGroup.isAdmin())
8289
}
8390

8491
@Test
@@ -115,6 +122,13 @@ class GroupTest {
115122
runBlocking { boGroup.sync() }
116123
assertEquals(alixGroup.memberAddresses().size, 2)
117124
assertEquals(boGroup.memberAddresses().size, 2)
125+
126+
assertEquals(boGroup.permissionLevel(), GroupPermissions.GROUP_CREATOR_IS_ADMIN)
127+
assertEquals(alixGroup.permissionLevel(), GroupPermissions.GROUP_CREATOR_IS_ADMIN)
128+
assertEquals(boGroup.adminAddress().lowercase(), boClient.address.lowercase())
129+
assertEquals(alixGroup.adminAddress().lowercase(), boClient.address.lowercase())
130+
assert(boGroup.isAdmin())
131+
assert(!alixGroup.isAdmin())
118132
}
119133

120134
@Test
@@ -306,6 +320,31 @@ class GroupTest {
306320
}
307321
}
308322

323+
@Test
324+
fun testCanStreamAllGroupMessages() = kotlinx.coroutines.test.runTest {
325+
val group = caroClient.conversations.newGroup(listOf(alix.walletAddress))
326+
alixClient.conversations.syncGroups()
327+
alixClient.conversations.streamAllGroupMessages().test {
328+
group.send("hi")
329+
assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8())
330+
group.send("hi again")
331+
assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8())
332+
}
333+
}
334+
335+
@Test
336+
fun testCanStreamAllMessages() = kotlinx.coroutines.test.runTest {
337+
val group = caroClient.conversations.newGroup(listOf(alix.walletAddress))
338+
val conversation = boClient.conversations.newConversation(alix.walletAddress)
339+
alixClient.conversations.syncGroups()
340+
alixClient.conversations.streamAllMessages(includeGroups = true).test {
341+
group.send("hi")
342+
assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8())
343+
conversation.send("hi again")
344+
assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8())
345+
}
346+
}
347+
309348
@Test
310349
fun testCanStreamDecryptedGroupMessages() = kotlinx.coroutines.test.runTest {
311350
val group = boClient.conversations.newGroup(listOf(alix.walletAddress))
@@ -319,6 +358,31 @@ class GroupTest {
319358
}
320359
}
321360

361+
@Test
362+
fun testCanStreamAllDecryptedGroupMessages() = kotlinx.coroutines.test.runTest {
363+
val group = caroClient.conversations.newGroup(listOf(alix.walletAddress))
364+
alixClient.conversations.syncGroups()
365+
alixClient.conversations.streamAllGroupDecryptedMessages().test {
366+
group.send("hi")
367+
assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8())
368+
group.send("hi again")
369+
assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8())
370+
}
371+
}
372+
373+
@Test
374+
fun testCanStreamAllDecryptedMessages() = kotlinx.coroutines.test.runTest {
375+
val group = caroClient.conversations.newGroup(listOf(alix.walletAddress))
376+
val conversation = boClient.conversations.newConversation(alix.walletAddress)
377+
alixClient.conversations.syncGroups()
378+
alixClient.conversations.streamAllDecryptedMessages(includeGroups = true).test {
379+
group.send("hi")
380+
assertEquals("hi", awaitItem().encodedContent.content.toStringUtf8())
381+
conversation.send("hi again")
382+
assertEquals("hi again", awaitItem().encodedContent.content.toStringUtf8())
383+
}
384+
}
385+
322386
@Test
323387
fun testCanStreamGroups() = kotlinx.coroutines.test.runTest {
324388
boClient.conversations.streamGroups().test {

library/src/main/java/org/xmtp/android/library/Conversation.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,7 @@ sealed class Conversation {
291291
is V1 -> conversationV1.decrypt(envelope)
292292
is V2 -> conversationV2.decrypt(envelope)
293293
is Group -> {
294-
if (message == null) {
295-
throw XMTPException("Groups require message be passed")
296-
} else {
297-
group.decrypt(message)
298-
}
294+
message?.decrypt() ?: throw XMTPException("Groups require message be passed")
299295
}
300296
}
301297
}

library/src/main/java/org/xmtp/android/library/Conversations.kt

+43-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.merge
1212
import kotlinx.coroutines.runBlocking
1313
import org.xmtp.android.library.GRPCApiClient.Companion.makeQueryRequest
1414
import org.xmtp.android.library.GRPCApiClient.Companion.makeSubscribeRequest
15+
import org.xmtp.android.library.libxmtp.Message
1516
import org.xmtp.android.library.messages.DecryptedMessage
1617
import org.xmtp.android.library.messages.Envelope
1718
import org.xmtp.android.library.messages.EnvelopeBuilder
@@ -39,6 +40,8 @@ import uniffi.xmtpv3.FfiConversationCallback
3940
import uniffi.xmtpv3.FfiConversations
4041
import uniffi.xmtpv3.FfiGroup
4142
import uniffi.xmtpv3.FfiListConversationsOptions
43+
import uniffi.xmtpv3.FfiMessage
44+
import uniffi.xmtpv3.FfiMessageCallback
4245
import uniffi.xmtpv3.GroupPermissions
4346
import java.util.Date
4447
import kotlin.time.Duration.Companion.nanoseconds
@@ -532,12 +535,34 @@ data class Conversations(
532535
awaitClose { stream.end() }
533536
}
534537

538+
fun streamAllGroupMessages(): Flow<DecodedMessage> = callbackFlow {
539+
val messageCallback = object : FfiMessageCallback {
540+
override fun onMessage(message: FfiMessage) {
541+
trySend(Message(client, message).decode())
542+
}
543+
}
544+
val stream = libXMTPConversations?.streamAllMessages(messageCallback)
545+
?: throw XMTPException("Client does not support Groups")
546+
awaitClose { stream.end() }
547+
}
548+
549+
fun streamAllGroupDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
550+
val messageCallback = object : FfiMessageCallback {
551+
override fun onMessage(message: FfiMessage) {
552+
trySend(Message(client, message).decrypt())
553+
}
554+
}
555+
val stream = libXMTPConversations?.streamAllMessages(messageCallback)
556+
?: throw XMTPException("Client does not support Groups")
557+
awaitClose { stream.end() }
558+
}
559+
535560
/**
536561
* Get the stream of all messages of the current [Client]
537562
* @return Flow object of [DecodedMessage] that represents all the messages of the
538563
* current [Client] as userInvite and userIntro
539564
*/
540-
fun streamAllMessages(): Flow<DecodedMessage> = flow {
565+
private fun streamAllV2Messages(): Flow<DecodedMessage> = flow {
541566
val topics = mutableListOf(
542567
Topic.userInvite(client.address).description,
543568
Topic.userIntro(client.address).description,
@@ -592,7 +617,23 @@ data class Conversations(
592617
}
593618
}
594619

595-
fun streamAllDecryptedMessages(): Flow<DecryptedMessage> = flow {
620+
fun streamAllMessages(includeGroups: Boolean = false): Flow<DecodedMessage> {
621+
return if (includeGroups) {
622+
merge(streamAllV2Messages(), streamAllGroupMessages())
623+
} else {
624+
streamAllV2Messages()
625+
}
626+
}
627+
628+
fun streamAllDecryptedMessages(includeGroups: Boolean = false): Flow<DecryptedMessage> {
629+
return if (includeGroups) {
630+
merge(streamAllV2DecryptedMessages(), streamAllGroupDecryptedMessages())
631+
} else {
632+
streamAllV2DecryptedMessages()
633+
}
634+
}
635+
636+
private fun streamAllV2DecryptedMessages(): Flow<DecryptedMessage> = flow {
596637
val topics = mutableListOf(
597638
Topic.userInvite(client.address).description,
598639
Topic.userIntro(client.address).description,

library/src/main/java/org/xmtp/android/library/Group.kt

+19-12
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import org.xmtp.android.library.messages.DecryptedMessage
1212
import org.xmtp.android.library.messages.PagingInfoSortDirection
1313
import org.xmtp.proto.message.api.v1.MessageApiOuterClass
1414
import uniffi.xmtpv3.FfiGroup
15+
import uniffi.xmtpv3.FfiGroupMetadata
1516
import uniffi.xmtpv3.FfiListMessagesOptions
1617
import uniffi.xmtpv3.FfiMessage
1718
import uniffi.xmtpv3.FfiMessageCallback
19+
import uniffi.xmtpv3.GroupPermissions
1820
import java.lang.Exception
1921
import java.util.Date
2022
import kotlin.time.Duration.Companion.nanoseconds
@@ -27,6 +29,9 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
2729
val createdAt: Date
2830
get() = Date(libXMTPGroup.createdAtNs() / 1_000_000)
2931

32+
private val metadata: FfiGroupMetadata
33+
get() = libXMTPGroup.groupMetadata()
34+
3035
fun send(text: String): String {
3136
return send(prepareMessage(content = text, options = null))
3237
}
@@ -113,7 +118,7 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
113118
limit = limit?.toLong()
114119
)
115120
).map {
116-
decrypt(Message(client, it))
121+
Message(client, it).decrypt()
117122
}
118123
when (direction) {
119124
MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING -> messages
@@ -122,20 +127,22 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
122127
}
123128
}
124129

125-
fun decrypt(message: Message): DecryptedMessage {
126-
return DecryptedMessage(
127-
id = message.id.toHex(),
128-
topic = message.convoId.toHex(),
129-
encodedContent = message.decode().encodedContent,
130-
senderAddress = message.senderAddress,
131-
sentAt = Date()
132-
)
133-
}
134-
135130
fun isActive(): Boolean {
136131
return libXMTPGroup.isActive()
137132
}
138133

134+
fun permissionLevel(): GroupPermissions {
135+
return metadata.policyType()
136+
}
137+
138+
fun isAdmin(): Boolean {
139+
return metadata.creatorAccountAddress().lowercase() == client.address.lowercase()
140+
}
141+
142+
fun adminAddress(): String {
143+
return metadata.creatorAccountAddress()
144+
}
145+
139146
fun addMembers(addresses: List<String>) {
140147
try {
141148
runBlocking { libXMTPGroup.addMembers(addresses) }
@@ -172,7 +179,7 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) {
172179
fun streamDecryptedMessages(): Flow<DecryptedMessage> = callbackFlow {
173180
val messageCallback = object : FfiMessageCallback {
174181
override fun onMessage(message: FfiMessage) {
175-
trySend(decrypt(Message(client, message)))
182+
trySend(Message(client, message).decrypt())
176183
}
177184
}
178185

library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.xmtp.android.library.Client
44
import org.xmtp.android.library.DecodedMessage
55
import org.xmtp.android.library.XMTPException
66
import org.xmtp.android.library.codecs.EncodedContent
7+
import org.xmtp.android.library.messages.DecryptedMessage
78
import org.xmtp.android.library.toHex
89
import uniffi.xmtpv3.FfiMessage
910
import java.util.Date
@@ -35,4 +36,14 @@ data class Message(val client: Client, private val libXMTPMessage: FfiMessage) {
3536
throw XMTPException("Error decoding message", e)
3637
}
3738
}
39+
40+
fun decrypt(): DecryptedMessage {
41+
return DecryptedMessage(
42+
id = id.toHex(),
43+
topic = convoId.toHex(),
44+
encodedContent = decode().encodedContent,
45+
senderAddress = senderAddress,
46+
sentAt = Date()
47+
)
48+
}
3849
}

0 commit comments

Comments
 (0)