Skip to content

Commit

Permalink
feat: add basic chat app, that sends and receives messages using `Cha…
Browse files Browse the repository at this point in the history
…tApi`
  • Loading branch information
ttypic committed Sep 5, 2024
1 parent 783cd4a commit 7c0ec73
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.**,^
ij_kotlin_imports_layout = *,^
51 changes: 5 additions & 46 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -19,6 +23,8 @@ android {
vectorDrawables {
useSupportLibrary = true
}

buildConfigField("String", "ABLY_KEY", "\"${getLocalProperty("ABLY_KEY") ?: ""}\"")
}

buildTypes {
Expand All @@ -38,6 +44,7 @@ android {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = true
compose = true
}
composeOptions {
Expand Down Expand Up @@ -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)
}
3 changes: 2 additions & 1 deletion example/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
android:theme="@style/Theme.AblyChatExample"
tools:targetApi="31">
<activity
android:windowSoftInputMode="adjustResize"
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
Expand All @@ -25,4 +26,4 @@
</activity>
</application>

</manifest>
</manifest>
150 changes: 138 additions & 12 deletions example/src/main/java/com/ably/chat/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
}
Expand All @@ -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<Message>()) }
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")
}
}
}
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
android.useAndroidX=true

android.enableBuildConfigAsBytecode=true

0 comments on commit 7c0ec73

Please sign in to comment.