From 7c0ec737ad6dffd486234aedae3af9484eaa97f9 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Sep 2024 12:01:19 +0100 Subject: [PATCH 1/2] feat: add basic chat app, that sends and receives messages using `ChatApi` --- .editorconfig | 2 +- .idea/codeStyles/Project.xml | 51 +----- example/build.gradle.kts | 17 ++ example/src/main/AndroidManifest.xml | 3 +- .../com/ably/chat/example/MainActivity.kt | 150 ++++++++++++++++-- gradle.properties | 4 +- 6 files changed, 166 insertions(+), 61 deletions(-) diff --git a/.editorconfig b/.editorconfig index b908e46c..f0486004 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,4 +7,4 @@ trim_trailing_whitespace = true charset = utf-8 [*.{kt,kts}] -ij_kotlin_imports_layout = android.**,androidx.**,*,java.**,javax.**,kotlin.**,kotlinx.**,io.ably.**,com.ably.**,^ \ No newline at end of file +ij_kotlin_imports_layout = *,^ \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index d69494f0..6c59a1fa 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -2,8 +2,8 @@ - \ No newline at end of file + diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 0f13d336..2307a660 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,3 +1,7 @@ +import java.io.FileInputStream +import java.io.InputStreamReader +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.android.kotlin) @@ -19,6 +23,8 @@ android { vectorDrawables { useSupportLibrary = true } + + buildConfigField("String", "ABLY_KEY", "\"${getLocalProperty("ABLY_KEY") ?: ""}\"") } buildTypes { @@ -38,6 +44,7 @@ android { jvmTarget = "1.8" } buildFeatures { + buildConfig = true compose = true } composeOptions { @@ -68,3 +75,13 @@ dependencies { debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } + +fun getLocalProperty(key: String, file: String = "local.properties"): String? { + val properties = Properties() + val localProperties = File(file) + if (!localProperties.isFile) return null + InputStreamReader(FileInputStream(localProperties), Charsets.UTF_8).use { reader -> + properties.load(reader) + } + return properties.getProperty(key) +} diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml index adb97014..a503e359 100644 --- a/example/src/main/AndroidManifest.xml +++ b/example/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ android:theme="@style/Theme.AblyChatExample" tools:targetApi="31"> - \ No newline at end of file + 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 792d3c6b..8fc363b0 100644 --- a/example/src/main/java/com/ably/chat/example/MainActivity.kt +++ b/example/src/main/java/com/ably/chat/example/MainActivity.kt @@ -4,24 +4,62 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +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.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import com.ably.chat.ChatApi +import com.ably.chat.Message +import com.ably.chat.QueryOptions +import com.ably.chat.QueryOptions.MessageOrder.OldestFirst +import com.ably.chat.RealtimeClient +import com.ably.chat.SendMessageParams import com.ably.chat.example.ui.theme.AblyChatExampleTheme +import io.ably.lib.types.ClientOptions +import java.util.UUID +import kotlinx.coroutines.launch + +val randomClientId = UUID.randomUUID().toString() class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val realtimeClient = RealtimeClient( + ClientOptions().apply { + key = BuildConfig.ABLY_KEY + clientId = randomClientId + logLevel = 2 + }, + ) + val chatApi = ChatApi(realtimeClient, randomClientId) enableEdgeToEdge() setContent { AblyChatExampleTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", + Chat( + chatApi, modifier = Modifier.padding(innerPadding), ) } @@ -31,17 +69,105 @@ class MainActivity : ComponentActivity() { } @Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier, - ) +fun Chat(chatApi: ChatApi, modifier: Modifier = Modifier) { + var messageText by remember { mutableStateOf(TextFieldValue("")) } + var sending by remember { mutableStateOf(false) } + var messages by remember { mutableStateOf(listOf()) } + val coroutineScope = rememberCoroutineScope() + + val roomId = "my-room" + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Button(modifier = modifier.align(Alignment.CenterHorizontally), onClick = { + coroutineScope.launch { + messages = chatApi.getMessages(roomId, QueryOptions(orderBy = OldestFirst)).items + } + }) { + Text("Load") + } + + LazyColumn( + modifier = Modifier.weight(1f).padding(16.dp), + userScrollEnabled = true, + ) { + items(messages.size) { index -> + MessageBubble(messages[index]) + } + } + + ChatInputField( + sending = sending, + messageInput = messageText, + onMessageChange = { messageText = it }, + ) { + sending = true + coroutineScope.launch { + chatApi.sendMessage( + roomId, + SendMessageParams( + text = messageText.text, + ), + ) + messageText = TextFieldValue("") + sending = false + } + } + } +} + +@Composable +fun MessageBubble(message: Message) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = if (message.clientId == randomClientId) Arrangement.End else Arrangement.Start, + ) { + Box( + modifier = Modifier + .background( + color = if (message.clientId != randomClientId) Color.Blue else Color.Gray, + shape = RoundedCornerShape(8.dp), + ) + .padding(12.dp), + ) { + Text( + text = message.text, + color = Color.White, + ) + } + } } -@Preview(showBackground = true) @Composable -fun GreetingPreview() { - AblyChatExampleTheme { - Greeting("Android") +fun ChatInputField( + sending: Boolean = false, + messageInput: TextFieldValue, + onMessageChange: (TextFieldValue) -> Unit, + onSendClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .imePadding(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + TextField( + value = messageInput, + onValueChange = onMessageChange, + readOnly = sending, + modifier = Modifier + .weight(1f) + .background(Color.White), + placeholder = { Text("Type a message...") }, + ) + Button(enabled = !sending, onClick = onSendClick) { + Text("Send") + } } } diff --git a/gradle.properties b/gradle.properties index 5a33dd51..5fd0d9f7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,6 @@ org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true + +android.enableBuildConfigAsBytecode=true From 69d4830517aa94b907a7e9270c89726698371faa Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Sep 2024 13:24:19 +0100 Subject: [PATCH 2/2] [CHAT-4902] feat: add chat room skeleton and default implementations for main classes In this PR most of the methods in the default implementations throws exceptions. We focused on happy path getting client, creating room, attaching to the room (without any corner cases) --- .idea/codeStyles/Project.xml | 10 ++-- build.gradle.kts | 1 + chat-android/build.gradle.kts | 9 ++++ .../src/main/java/com/ably/chat/ChatClient.kt | 25 ++++++++- .../src/main/java/com/ably/chat/Messages.kt | 39 ++++++++++++++ .../src/main/java/com/ably/chat/Occupancy.kt | 29 +++++++++++ .../src/main/java/com/ably/chat/Presence.kt | 46 +++++++++++++++++ .../src/main/java/com/ably/chat/Room.kt | 51 +++++++++++++++++++ .../main/java/com/ably/chat/RoomReactions.kt | 32 ++++++++++++ .../src/main/java/com/ably/chat/Rooms.kt | 41 +++++++++++++++ .../src/main/java/com/ably/chat/Typing.kt | 40 +++++++++++++++ .../src/main/java/com/ably/chat/Utils.kt | 46 +++++++++++++++++ detekt.yml | 2 +- gradle/libs.versions.toml | 5 +- 14 files changed, 366 insertions(+), 10 deletions(-) create mode 100644 chat-android/src/main/java/com/ably/chat/Utils.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 6c59a1fa..8cbc9db8 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,10 +1,7 @@ - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 872ff85a..593d602a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.kotlin) apply false alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.build.config) apply false } dependencies { diff --git a/chat-android/build.gradle.kts b/chat-android/build.gradle.kts index 9fdde4f7..9f116f4b 100644 --- a/chat-android/build.gradle.kts +++ b/chat-android/build.gradle.kts @@ -1,8 +1,11 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.android.kotlin) + alias(libs.plugins.build.config) } +val version = libs.versions.ably.chat.get() + android { namespace = "com.ably.chat" compileSdk = 34 @@ -33,6 +36,12 @@ android { } } +buildConfig { + packageName("com.ably.chat") + useKotlinOutput { internalVisibility = true } + buildConfigField("APP_VERSION", provider { "\"${version}\"" }) +} + dependencies { api(libs.ably.android) implementation(libs.gson) diff --git a/chat-android/src/main/java/com/ably/chat/ChatClient.kt b/chat-android/src/main/java/com/ably/chat/ChatClient.kt index 6a1d06d9..b621db9b 100644 --- a/chat-android/src/main/java/com/ably/chat/ChatClient.kt +++ b/chat-android/src/main/java/com/ably/chat/ChatClient.kt @@ -1,7 +1,8 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import io.ably.lib.realtime.AblyRealtime -import io.ably.lib.types.ClientOptions typealias RealtimeClient = AblyRealtime @@ -35,3 +36,25 @@ interface ChatClient { */ val clientOptions: ClientOptions } + +fun ChatClient(realtimeClient: RealtimeClient, clientOptions: ClientOptions): ChatClient = DefaultChatClient(realtimeClient, clientOptions) + +internal class DefaultChatClient( + override val realtime: RealtimeClient, + override val clientOptions: ClientOptions, +) : ChatClient { + + private val chatApi = ChatApi(realtime, clientId) + + override val rooms: Rooms = DefaultRooms( + realtimeClient = realtime, + chatApi = chatApi, + clientOptions = clientOptions, + ) + + override val connection: Connection + get() = TODO("Not yet implemented") + + override val clientId: String + get() = realtime.auth.clientId +} diff --git a/chat-android/src/main/java/com/ably/chat/Messages.kt b/chat-android/src/main/java/com/ably/chat/Messages.kt index a43259c7..854b147b 100644 --- a/chat-android/src/main/java/com/ably/chat/Messages.kt +++ b/chat-android/src/main/java/com/ably/chat/Messages.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import io.ably.lib.realtime.Channel @@ -173,3 +175,40 @@ data class SendMessageParams( */ val headers: MessageHeaders? = null, ) + +class DefaultMessages( + private val roomId: String, + private val realtimeClient: RealtimeClient, + private val chatApi: ChatApi, +) : Messages { + + /** + * the channel name for the chat messages channel. + */ + private val messagesChannelName = "$roomId::\$chat::\$chatMessages" + + override val channel: Channel + get() = realtimeClient.channels.get(messagesChannelName, ChatChannelOptions()) + + override fun subscribe(listener: Messages.Listener) { + TODO("Not yet implemented") + } + + override fun unsubscribe(listener: Messages.Listener) { + TODO("Not yet implemented") + } + + override suspend fun get(options: QueryOptions): PaginatedResult { + TODO("Not yet implemented") + } + + override suspend fun send(params: SendMessageParams): Message = chatApi.sendMessage(roomId, params) + + override fun onDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } + + override fun offDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Occupancy.kt b/chat-android/src/main/java/com/ably/chat/Occupancy.kt index 0ebe721c..62a6bb09 100644 --- a/chat-android/src/main/java/com/ably/chat/Occupancy.kt +++ b/chat-android/src/main/java/com/ably/chat/Occupancy.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import io.ably.lib.realtime.Channel @@ -63,3 +65,30 @@ data class OccupancyEvent( */ val presenceMembers: Int, ) + +internal class DefaultOccupancy( + private val messages: Messages, +) : Occupancy { + override val channel: Channel + get() = messages.channel + + override fun subscribe(listener: Occupancy.Listener) { + TODO("Not yet implemented") + } + + override fun unsubscribe(listener: Occupancy.Listener) { + TODO("Not yet implemented") + } + + override suspend fun get(): OccupancyEvent { + TODO("Not yet implemented") + } + + override fun onDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } + + override fun offDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Presence.kt b/chat-android/src/main/java/com/ably/chat/Presence.kt index c2c55b1f..ba492962 100644 --- a/chat-android/src/main/java/com/ably/chat/Presence.kt +++ b/chat-android/src/main/java/com/ably/chat/Presence.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import android.text.PrecomputedText.Params @@ -132,3 +134,47 @@ data class PresenceEvent( */ val data: PresenceData, ) + +internal class DefaultPresence( + private val messages: Messages, +) : Presence { + + override val channel: Channel + get() = messages.channel + + override suspend fun get(params: List): List { + TODO("Not yet implemented") + } + + override suspend fun isUserPresent(clientId: String): Boolean { + TODO("Not yet implemented") + } + + override suspend fun enter(data: PresenceData?) { + TODO("Not yet implemented") + } + + override suspend fun update(data: PresenceData?) { + TODO("Not yet implemented") + } + + override suspend fun leave(data: PresenceData?) { + TODO("Not yet implemented") + } + + override fun subscribe(listener: Presence.Listener) { + TODO("Not yet implemented") + } + + override fun unsubscribe(listener: Presence.Listener) { + TODO("Not yet implemented") + } + + override fun onDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } + + override fun offDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Room.kt b/chat-android/src/main/java/com/ably/chat/Room.kt index 3f6616e5..8550eb7c 100644 --- a/chat-android/src/main/java/com/ably/chat/Room.kt +++ b/chat-android/src/main/java/com/ably/chat/Room.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat /** @@ -80,3 +82,52 @@ interface Room { */ suspend fun detach() } + +internal class DefaultRoom( + override val roomId: String, + override val options: RoomOptions, + realtimeClient: RealtimeClient, + chatApi: ChatApi, +) : Room { + + override val messages: Messages = DefaultMessages( + roomId = roomId, + realtimeClient = realtimeClient, + chatApi = chatApi, + ) + + override val presence: Presence = DefaultPresence( + messages = messages, + ) + + override val reactions: RoomReactions = DefaultRoomReactions( + roomId = roomId, + realtimeClient = realtimeClient, + ) + + override val typing: Typing = DefaultTyping( + roomId = roomId, + realtimeClient = realtimeClient, + ) + + override val occupancy: Occupancy = DefaultOccupancy( + messages = messages, + ) + + override val status: RoomStatus + get() { + TODO("Not yet implemented") + } + + override suspend fun attach() { + messages.channel.attachCoroutine() + typing.channel.attachCoroutine() + reactions.channel.attachCoroutine() + } + + override suspend fun detach() { + messages.channel.detachCoroutine() + typing.channel.detachCoroutine() + reactions.channel.detachCoroutine() + } +} 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 3ced2e2f..f3a2d588 100644 --- a/chat-android/src/main/java/com/ably/chat/RoomReactions.kt +++ b/chat-android/src/main/java/com/ably/chat/RoomReactions.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import io.ably.lib.realtime.Channel @@ -100,3 +102,33 @@ data class SendReactionParams( */ val headers: ReactionHeaders? = null, ) + +internal class DefaultRoomReactions( + roomId: String, + private val realtimeClient: RealtimeClient, +) : RoomReactions { + private val roomReactionsChannelName = "$roomId::\$chat::\$reactions" + + override val channel: Channel + get() = realtimeClient.channels.get(roomReactionsChannelName, ChatChannelOptions()) + + override suspend fun send(params: SendReactionParams) { + TODO("Not yet implemented") + } + + override fun subscribe(listener: RoomReactions.Listener) { + TODO("Not yet implemented") + } + + override fun unsubscribe(listener: RoomReactions.Listener) { + TODO("Not yet implemented") + } + + override fun onDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } + + override fun offDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Rooms.kt b/chat-android/src/main/java/com/ably/chat/Rooms.kt index e4fd7ff6..44d917d0 100644 --- a/chat-android/src/main/java/com/ably/chat/Rooms.kt +++ b/chat-android/src/main/java/com/ably/chat/Rooms.kt @@ -1,5 +1,8 @@ package com.ably.chat +import io.ably.lib.types.AblyException +import io.ably.lib.types.ErrorInfo + /** * Manages the lifecycle of chat rooms. */ @@ -35,3 +38,41 @@ interface Rooms { */ suspend fun release(roomId: String) } + +/** + * Manages the chat rooms. + */ +internal class DefaultRooms( + private val realtimeClient: RealtimeClient, + private val chatApi: ChatApi, + override val clientOptions: ClientOptions, +) : Rooms { + private val roomIdToRoom: MutableMap = mutableMapOf() + + override fun get(roomId: String, options: RoomOptions): Room { + return synchronized(this) { + val room = roomIdToRoom.getOrPut(roomId) { + DefaultRoom( + roomId = roomId, + options = options, + realtimeClient = realtimeClient, + chatApi = chatApi, + ) + } + + if (room.options != options) { + throw AblyException.fromErrorInfo( + ErrorInfo("Room already exists with different options", HttpStatusCodes.BadRequest, ErrorCodes.BadRequest), + ) + } + + room + } + } + + override suspend fun release(roomId: String) { + synchronized(this) { + roomIdToRoom.remove(roomId) + } + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Typing.kt b/chat-android/src/main/java/com/ably/chat/Typing.kt index c6532af5..fa75bc24 100644 --- a/chat-android/src/main/java/com/ably/chat/Typing.kt +++ b/chat-android/src/main/java/com/ably/chat/Typing.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication", "NotImplementedDeclaration") + package com.ably.chat import io.ably.lib.realtime.Channel @@ -77,3 +79,41 @@ interface Typing : EmitsDiscontinuities { * Represents a typing event. */ data class TypingEvent(val currentlyTyping: Set) + +internal class DefaultTyping( + roomId: String, + private val realtimeClient: RealtimeClient, +) : Typing { + private val typingIndicatorsChannelName = "$roomId::\$chat::\$typingIndicators" + + override val channel: Channel + get() = realtimeClient.channels.get(typingIndicatorsChannelName, ChatChannelOptions()) + + override fun subscribe(listener: Typing.Listener) { + TODO("Not yet implemented") + } + + override fun unsubscribe(listener: Typing.Listener) { + TODO("Not yet implemented") + } + + override suspend fun get(): Set { + TODO("Not yet implemented") + } + + override suspend fun start() { + TODO("Not yet implemented") + } + + override suspend fun stop() { + TODO("Not yet implemented") + } + + override fun onDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } + + override fun offDiscontinuity(listener: EmitsDiscontinuities.Listener) { + TODO("Not yet implemented") + } +} diff --git a/chat-android/src/main/java/com/ably/chat/Utils.kt b/chat-android/src/main/java/com/ably/chat/Utils.kt new file mode 100644 index 00000000..531a4eb3 --- /dev/null +++ b/chat-android/src/main/java/com/ably/chat/Utils.kt @@ -0,0 +1,46 @@ +package com.ably.chat + +import io.ably.lib.realtime.Channel +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.types.AblyException +import io.ably.lib.types.ChannelOptions +import io.ably.lib.types.ErrorInfo +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +const val AGENT_PARAMETER_NAME = "agent" + +suspend fun Channel.attachCoroutine() = suspendCoroutine { continuation -> + attach(object : CompletionListener { + override fun onSuccess() { + continuation.resume(Unit) + } + + override fun onError(reason: ErrorInfo?) { + continuation.resumeWithException(AblyException.fromErrorInfo(reason)) + } + }) +} + +suspend fun Channel.detachCoroutine() = suspendCoroutine { continuation -> + detach(object : CompletionListener { + override fun onSuccess() { + continuation.resume(Unit) + } + + override fun onError(reason: ErrorInfo?) { + continuation.resumeWithException(AblyException.fromErrorInfo(reason)) + } + }) +} + +@Suppress("FunctionName") +fun ChatChannelOptions(init: (ChannelOptions.() -> Unit)? = null): ChannelOptions { + val options = ChannelOptions() + init?.let { options.it() } + options.params = options.params + mapOf( + AGENT_PARAMETER_NAME to "chat-kotlin/${BuildConfig.APP_VERSION}", + ) + return options +} diff --git a/detekt.yml b/detekt.yml index 3995e03b..95cd7e46 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1000,7 +1000,7 @@ style: UseCheckOrError: active: true UseDataClass: - active: true + active: false allowVars: false UseEmptyCounterpart: active: false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bc87085..7b95041b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,11 +2,12 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] +ably-chat = "0.0.1" +ably = "1.2.41" junit = "4.13.2" agp = "8.5.2" detekt = "1.23.6" kotlin = "2.0.10" -ably = "1.2.41" androidx-test = "1.6.1" androidx-junit = "1.2.1" core-ktx = "1.13.1" @@ -17,6 +18,7 @@ compose-bom = "2024.06.00" gson = "2.11.0" mockk = "1.13.12" coroutine = "1.8.1" +build-config = "5.4.0" [libraries] junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -52,3 +54,4 @@ android-kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +build-config = { id = "com.github.gmazzo.buildconfig", version.ref = "build-config" }