From 94121075e494bc7c5f8696d267454e0b9a170f36 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 5 Nov 2024 14:12:23 +0000 Subject: [PATCH] [ECO-5082] feat: add presence popup for example app --- .../com/ably/chat/example/MainActivity.kt | 49 ++++++-- .../java/com/ably/chat/example/Settings.kt | 5 + .../com/ably/chat/example/ui/PresencePopup.kt | 109 ++++++++++++++++++ 3 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 example/src/main/java/com/ably/chat/example/Settings.kt create mode 100644 example/src/main/java/com/ably/chat/example/ui/PresencePopup.kt 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 4a851c0..19afd48 100644 --- a/example/src/main/java/com/ably/chat/example/MainActivity.kt +++ b/example/src/main/java/com/ably/chat/example/MainActivity.kt @@ -16,10 +16,16 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -38,6 +44,7 @@ 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.PresencePopup import com.ably.chat.example.ui.theme.AblyChatExampleTheme import io.ably.lib.types.ClientOptions import java.util.UUID @@ -62,17 +69,40 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { AblyChatExampleTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Chat( - chatClient, - modifier = Modifier.padding(innerPadding), - ) - } + App(chatClient) } } } } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun App(chatClient: ChatClient) { + var showPopup by remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = { Text("Chat") }, + actions = { + IconButton(onClick = { showPopup = true }) { + Icon(Icons.Default.Person, contentDescription = "Show members") + } + }, + ) + }, + ) { innerPadding -> + Chat( + chatClient, + modifier = Modifier.padding(innerPadding), + ) + if (showPopup) { + PresencePopup(chatClient, onDismiss = { showPopup = false }) + } + } +} + @SuppressWarnings("LongMethod") @Composable fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { @@ -83,8 +113,7 @@ fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { val coroutineScope = rememberCoroutineScope() var receivedReactions by remember { mutableStateOf>(listOf()) } - val roomId = "my-room" - val room = chatClient.rooms.get(roomId) + val room = chatClient.rooms.get(Settings.ROOM_ID) DisposableEffect(Unit) { coroutineScope.launch { @@ -130,7 +159,9 @@ fun Chat(chatClient: ChatClient, modifier: Modifier = Modifier) { verticalArrangement = Arrangement.SpaceBetween, ) { LazyColumn( - modifier = Modifier.weight(1f).padding(16.dp), + modifier = Modifier + .weight(1f) + .padding(16.dp), userScrollEnabled = true, state = listState, ) { diff --git a/example/src/main/java/com/ably/chat/example/Settings.kt b/example/src/main/java/com/ably/chat/example/Settings.kt new file mode 100644 index 0000000..e169402 --- /dev/null +++ b/example/src/main/java/com/ably/chat/example/Settings.kt @@ -0,0 +1,5 @@ +package com.ably.chat.example + +object Settings { + const val ROOM_ID = "my-room" +} diff --git a/example/src/main/java/com/ably/chat/example/ui/PresencePopup.kt b/example/src/main/java/com/ably/chat/example/ui/PresencePopup.kt new file mode 100644 index 0000000..cfc52c2 --- /dev/null +++ b/example/src/main/java/com/ably/chat/example/ui/PresencePopup.kt @@ -0,0 +1,109 @@ +package com.ably.chat.example.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.text.BasicText +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import com.ably.chat.ChatClient +import com.ably.chat.PresenceMember +import com.ably.chat.Subscription +import com.ably.chat.example.Settings +import com.google.gson.JsonObject +import kotlinx.coroutines.launch + +@SuppressWarnings("LongMethod") +@Composable +fun PresencePopup(chatClient: ChatClient, onDismiss: () -> Unit) { + var members by remember { mutableStateOf(listOf()) } + val coroutineScope = rememberCoroutineScope() + val presence = chatClient.rooms.get(Settings.ROOM_ID).presence + + DisposableEffect(Unit) { + var subscription: Subscription? = null + + coroutineScope.launch { + members = presence.get() + subscription = presence.subscribe { + coroutineScope.launch { + members = presence.get() + } + } + } + + onDispose { + subscription?.unsubscribe() + } + } + + Popup( + onDismissRequest = onDismiss, + ) { + Surface( + modifier = Modifier.padding(16.dp), + shape = MaterialTheme.shapes.medium, + shadowElevation = 8.dp, + ) { + Column( + modifier = Modifier + .padding(16.dp) + .wrapContentWidth(), + ) { + Text("Chat Members", style = MaterialTheme.typography.headlineMedium) + Spacer(modifier = Modifier.height(8.dp)) + members.forEach { member -> + BasicText("${member.clientId} - (${(member.data as? JsonObject)?.get("status")?.asString})") + Spacer(modifier = Modifier.height(4.dp)) + } + Spacer(modifier = Modifier.height(8.dp)) + Button(onClick = { + coroutineScope.launch { + presence.enter( + JsonObject().apply { + addProperty("status", "online") + }, + ) + } + }) { + Text("Join") + } + Button(onClick = { + coroutineScope.launch { + presence.enter( + JsonObject().apply { + addProperty("status", "away") + }, + ) + } + }) { + Text("Appear away") + } + Button(onClick = { + coroutineScope.launch { + presence.leave() + } + }) { + Text("Leave") + } + Button(onClick = onDismiss) { + Text("Close") + } + } + } + } +}