diff --git a/chat-android/src/main/java/com/ably/chat/RoomReactions.kt b/chat-android/src/main/java/com/ably/chat/RoomReactions.kt index f58b712..3fab956 100644 --- a/chat-android/src/main/java/com/ably/chat/RoomReactions.kt +++ b/chat-android/src/main/java/com/ably/chat/RoomReactions.kt @@ -117,6 +117,7 @@ internal class DefaultRoomReactions( // (CHA-ER3a) Reactions are sent on the channel using a message in a particular format - see spec for format. override suspend fun send(params: SendReactionParams) { val pubSubMessage = PubSubMessage().apply { + name = RoomReactionEventType.Reaction.eventName data = JsonObject().apply { addProperty("type", params.type) params.metadata?.let { add("metadata", it.toJson()) } @@ -145,7 +146,7 @@ internal class DefaultRoomReactions( createdAt = pubSubMessage.timestamp, clientId = pubSubMessage.clientId, metadata = data.get("metadata")?.toMap() ?: mapOf(), - headers = pubSubMessage.extras.asJsonObject().get("headers")?.toMap() ?: mapOf(), + headers = pubSubMessage.extras?.asJsonObject()?.get("headers")?.toMap() ?: mapOf(), isSelf = pubSubMessage.clientId == clientId, ) listener.onReaction(reaction) diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 2307a66..dc7b801 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.konfetti.compose) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/example/src/main/java/com/ably/chat/example/MainActivity.kt b/example/src/main/java/com/ably/chat/example/MainActivity.kt index d12596a..4a851c0 100644 --- a/example/src/main/java/com/ably/chat/example/MainActivity.kt +++ b/example/src/main/java/com/ably/chat/example/MainActivity.kt @@ -37,6 +37,7 @@ import com.ably.chat.ChatClient import com.ably.chat.Message import com.ably.chat.RealtimeClient import com.ably.chat.SendMessageParams +import com.ably.chat.SendReactionParams import com.ably.chat.example.ui.theme.AblyChatExampleTheme import io.ably.lib.types.ClientOptions import java.util.UUID @@ -72,6 +73,7 @@ class MainActivity : ComponentActivity() { } } +@SuppressWarnings("LongMethod") @Composable fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { var messageText by remember { mutableStateOf(TextFieldValue("")) } @@ -79,10 +81,22 @@ fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { var messages by remember { mutableStateOf(listOf()) } val listState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() + var receivedReactions by remember { mutableStateOf>(listOf()) } val roomId = "my-room" val room = chatClient.rooms.get(roomId) + DisposableEffect(Unit) { + coroutineScope.launch { + room.attach() + } + onDispose { + coroutineScope.launch { + room.detach() + } + } + } + DisposableEffect(Unit) { val subscription = room.messages.subscribe { messages += it.message @@ -101,6 +115,16 @@ fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { } } + DisposableEffect(Unit) { + val subscription = room.reactions.subscribe { + receivedReactions += it.type + } + + onDispose { + subscription.unsubscribe() + } + } + Column( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween, @@ -119,17 +143,26 @@ fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { sending = sending, messageInput = messageText, onMessageChange = { messageText = it }, - ) { - sending = true - coroutineScope.launch { - room.messages.send( - SendMessageParams( - text = messageText.text, - ), - ) - messageText = TextFieldValue("") - sending = false - } + onSendClick = { + sending = true + coroutineScope.launch { + room.messages.send( + SendMessageParams( + text = messageText.text, + ), + ) + messageText = TextFieldValue("") + sending = false + } + }, + onReactionClick = { + coroutineScope.launch { + room.reactions.send(SendReactionParams(type = "\uD83D\uDC4D")) + } + }, + ) + if (receivedReactions.isNotEmpty()) { + Text("Received reactions: ${receivedReactions.joinToString()}", modifier = Modifier.padding(16.dp)) } } } @@ -164,6 +197,7 @@ fun ChatInputField( messageInput: TextFieldValue, onMessageChange: (TextFieldValue) -> Unit, onSendClick: () -> Unit, + onReactionClick: () -> Unit, ) { Row( modifier = Modifier @@ -181,8 +215,14 @@ fun ChatInputField( .background(Color.White), placeholder = { Text("Type a message...") }, ) - Button(enabled = !sending, onClick = onSendClick) { - Text("Send") + if (messageInput.text.isNotEmpty()) { + Button(enabled = !sending, onClick = onSendClick) { + Text("Send") + } + } else { + Button(onClick = onReactionClick) { + Text("\uD83D\uDC4D") + } } } } @@ -214,6 +254,7 @@ fun ChatInputPreview() { messageInput = TextFieldValue(""), onMessageChange = {}, onSendClick = {}, + onReactionClick = {}, ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3817db2..35bce2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ ably = "1.2.43" junit = "4.13.2" agp = "8.5.2" detekt = "1.23.6" +konfetti-compose = "2.0.4" kotlin = "2.0.10" androidx-test = "1.6.1" androidx-junit = "1.2.1" @@ -43,6 +44,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +konfetti-compose = { module = "nl.dionsegijn:konfetti-compose", version.ref = "konfetti-compose" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } coroutine-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutine" }