Skip to content

Commit 4fe9dfb

Browse files
committed
Update tutorial for Compose 1.0.0 and add customization steps
1 parent 4ec47cf commit 4fe9dfb

File tree

10 files changed

+477
-167
lines changed

10 files changed

+477
-167
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,26 @@ The project is pre-configured with a shared [Stream](https://getstream.io) accou
99
1. Clone the repository
1010
2. Open the project in Android Studio (Arctic Fox or later)
1111
3. Run the _app_
12+
4. Make sure to check the [Details](#details) section below for the different steps
13+
14+
## Details
15+
16+
The tutorial app consists of two screens:
17+
18+
* `MainActivity`: Shows the list of available channels.
19+
* `MessagesActivity`: Shows the selected channel view, which includes the header, message list, and message input view.
20+
21+
There are a handful of `MessagesActivity` implementations, which correspond to the steps of the tutorial. You can easily swap them by changing the `onItemClick` handler located in `MainActivity`:
22+
23+
```kotlin
24+
onItemClick = { channel ->
25+
startActivity(MessagesActivity4.getIntent(this, channel.cid))
26+
},
27+
```
28+
29+
You can choose from four different `MessagesActivity` implementations:
30+
31+
* `MessagesActivity` - a basic _Message Screen_ implementation
32+
* `MessagesActivity2` - includes customization of the screen by using `ChatTheme`
33+
* `MessagesActivity3` - uses bound and stateless components to build the chat screen, with further customization
34+
* `MessagesActivity4` - uses a custom message composer component for extended customization

app/build.gradle

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ plugins {
55

66
android {
77
compileSdk 30
8-
buildToolsVersion "31.0.0"
98

109
defaultConfig {
1110
applicationId "com.example.chattutorial"
@@ -41,23 +40,30 @@ android {
4140
kotlinCompilerExtensionVersion compose_version
4241
kotlinCompilerVersion '1.5.10'
4342
}
43+
packagingOptions {
44+
resources {
45+
excludes += '/META-INF/{AL2.0,LGPL2.1}'
46+
}
47+
}
4448
}
4549

4650
dependencies {
47-
def streamVersion = "4.15.0-beta"
48-
implementation "io.getstream:stream-chat-android-compose:$streamVersion"
51+
def streamVersion = "4.15.0"
52+
implementation "io.getstream:stream-chat-android-compose:$streamVersion-beta"
4953

50-
implementation 'androidx.core:core-ktx:1.6.0'
51-
implementation 'androidx.appcompat:appcompat:1.3.1'
52-
implementation 'com.google.android.material:material:1.4.0'
54+
implementation "androidx.compose.material:material-icons-extended:$compose_version"
55+
56+
implementation 'androidx.core:core-ktx:1.3.2'
57+
implementation 'androidx.appcompat:appcompat:1.2.0'
58+
implementation 'com.google.android.material:material:1.3.0'
5359
implementation "androidx.compose.ui:ui:$compose_version"
5460
implementation "androidx.compose.material:material:$compose_version"
5561
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
5662
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
57-
implementation 'androidx.activity:activity-compose:1.3.0'
63+
implementation 'androidx.activity:activity-compose:1.3.0-alpha06'
5864
testImplementation 'junit:junit:4.+'
59-
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
60-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
65+
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
66+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
6167
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
6268
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
63-
}
69+
}

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
<activity
1313
android:name=".MessagesActivity"
1414
android:exported="true" />
15+
<activity
16+
android:name=".MessagesActivity2"
17+
android:exported="true" />
18+
<activity
19+
android:name=".MessagesActivity3"
20+
android:exported="true" />
21+
<activity
22+
android:name=".MessagesActivity4"
23+
android:exported="true" />
1524
<activity
1625
android:name=".MainActivity"
1726
android:exported="true"

app/src/main/java/com/example/chattutorial/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class MainActivity : AppCompatActivity() {
4141
ChannelsScreen(
4242
title = stringResource(id = R.string.app_name),
4343
onItemClick = { channel ->
44-
startActivity(MessagesActivity.getIntent(this, channel.cid))
44+
startActivity(MessagesActivity4.getIntent(this, channel.cid))
4545
},
4646
onBackPressed = { finish() }
4747
)
Lines changed: 7 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,15 @@
11
package com.example.chattutorial
22

3-
import android.content.ClipboardManager
43
import android.content.Context
54
import android.content.Intent
65
import android.os.Bundle
76
import androidx.activity.compose.setContent
87
import androidx.appcompat.app.AppCompatActivity
9-
import androidx.compose.foundation.layout.*
10-
import androidx.compose.foundation.shape.RoundedCornerShape
11-
import androidx.compose.material.Icon
12-
import androidx.compose.material.Scaffold
13-
import androidx.compose.material.Text
14-
import androidx.compose.material.icons.Icons
15-
import androidx.compose.runtime.Composable
16-
import androidx.compose.ui.Alignment
17-
import androidx.compose.ui.Modifier
18-
import androidx.compose.ui.graphics.RectangleShape
19-
import androidx.compose.ui.unit.dp
20-
import io.getstream.chat.android.client.ChatClient
21-
import io.getstream.chat.android.compose.state.messages.Thread
22-
import io.getstream.chat.android.compose.ui.messages.attachments.AttachmentsPicker
23-
import io.getstream.chat.android.compose.ui.messages.composer.MessageComposer
24-
import io.getstream.chat.android.compose.ui.messages.composer.components.MessageInput
25-
import io.getstream.chat.android.compose.ui.messages.list.MessageList
26-
import io.getstream.chat.android.compose.ui.messages.overlay.SelectedMessageOverlay
27-
import io.getstream.chat.android.compose.ui.messages.overlay.defaultMessageOptions
8+
import io.getstream.chat.android.compose.ui.messages.MessagesScreen
289
import io.getstream.chat.android.compose.ui.theme.ChatTheme
29-
import io.getstream.chat.android.compose.ui.theme.StreamShapes
30-
import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerViewModel
31-
import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel
32-
import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel
33-
import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory
34-
import io.getstream.chat.android.offline.ChatDomain
3510

3611
class MessagesActivity : AppCompatActivity() {
3712

38-
// Build the ViewModel factory
39-
private val factory by lazy {
40-
MessagesViewModelFactory(
41-
this,
42-
getSystemService(CLIPBOARD_SERVICE) as ClipboardManager,
43-
ChatClient.instance(),
44-
ChatDomain.instance(),
45-
intent.getStringExtra(KEY_CHANNEL_ID) ?: "",
46-
30
47-
)
48-
}
49-
50-
// Build the required ViewModels, using the 'factory'
51-
private val listViewModel: MessageListViewModel by viewModels { factory }
52-
private val attachmentsPickerViewModel: AttachmentsPickerViewModel by viewModels { factory }
53-
private val composerViewModel: MessageComposerViewModel by viewModels { factory }
54-
5513
override fun onCreate(savedInstanceState: Bundle?) {
5614
super.onCreate(savedInstanceState)
5715
// 1 - Load the ID of the selected channel
@@ -64,121 +22,16 @@ class MessagesActivity : AppCompatActivity() {
6422

6523
// 2 - Add the MessagesScreen to your UI
6624
setContent {
67-
ChatTheme(
68-
shapes = StreamShapes(
69-
avatar = RoundedCornerShape(8.dp),
70-
attachment = RoundedCornerShape(16.dp),
71-
myMessageBubble = RoundedCornerShape(16.dp),
72-
otherMessageBubble = RoundedCornerShape(16.dp),
73-
inputField = RectangleShape
74-
)
75-
) {
76-
MyCustomUi()
77-
}
78-
}
79-
}
80-
81-
@Composable
82-
fun MyCustomUi() {
83-
// load the data
84-
val isShowingAttachments = attachmentsPickerViewModel.isShowingAttachments
85-
val selectedMessage = listViewModel.currentMessagesState.selectedMessage
86-
val user by listViewModel.user.collectAsState()
87-
88-
Box(modifier = Modifier.fillMaxSize()) {
89-
Scaffold(
90-
modifier = Modifier.fillMaxSize(),
91-
bottomBar = {
92-
MyCustomComposer()
93-
}
94-
) {
95-
MessageList( // build the MessageList and connect the actions
96-
modifier = Modifier
97-
.padding(it)
98-
.fillMaxSize(),
99-
viewModel = listViewModel,
100-
onThreadClick = { message ->
101-
composerViewModel.setMessageMode(Thread(message))
102-
listViewModel.openMessageThread(message)
103-
}
104-
)
105-
}
106-
107-
// show attachments picker when necessary
108-
if (isShowingAttachments) {
109-
AttachmentsPicker(
110-
attachmentsPickerViewModel = attachmentsPickerViewModel,
111-
modifier = Modifier
112-
.align(Alignment.BottomCenter)
113-
.height(350.dp),
114-
onAttachmentsSelected = { attachments ->
115-
attachmentsPickerViewModel.changeAttachmentState(false)
116-
composerViewModel.addSelectedAttachments(attachments)
117-
},
118-
onDismiss = {
119-
attachmentsPickerViewModel.changeAttachmentState(false)
120-
attachmentsPickerViewModel.dismissAttachments()
121-
}
122-
)
123-
}
124-
125-
// Show the overlay if we've selected a message
126-
if (selectedMessage != null) {
127-
SelectedMessageOverlay(
128-
messageOptions = defaultMessageOptions(selectedMessage, user, listViewModel.isInThread),
129-
message = selectedMessage,
130-
onMessageAction = { action ->
131-
composerViewModel.performMessageAction(action)
132-
listViewModel.performMessageAction(action)
133-
},
134-
onDismiss = { listViewModel.removeOverlay() }
25+
ChatTheme {
26+
MessagesScreen(
27+
channelId = channelId,
28+
messageLimit = 30,
29+
onBackPressed = { finish() }
13530
)
13631
}
13732
}
13833
}
13934

140-
@Composable
141-
fun MyCustomComposer() {
142-
MessageComposer(
143-
modifier = Modifier
144-
.fillMaxWidth()
145-
.wrapContentHeight(),
146-
viewModel = composerViewModel,
147-
integrations = {},
148-
input = {
149-
MessageInput(
150-
modifier = Modifier
151-
.fillMaxWidth()
152-
.weight(7f)
153-
.padding(start = 8.dp),
154-
value = composerViewModel.input,
155-
attachments = composerViewModel.selectedAttachments,
156-
activeAction = composerViewModel.activeAction,
157-
onValueChange = { composerViewModel.setMessageInput(it) },
158-
onAttachmentRemoved = { composerViewModel.removeSelectedAttachment(it) },
159-
label = {
160-
Row(
161-
Modifier.wrapContentWidth(),
162-
verticalAlignment = Alignment.CenterVertically
163-
) {
164-
Icon(
165-
imageVector = Icons.Default.Keyboard,
166-
contentDescription = null,
167-
tint = ChatTheme.colors.textLowEmphasis
168-
)
169-
170-
Text(
171-
modifier = Modifier.padding(start = 4.dp),
172-
text = "Type something",
173-
color = ChatTheme.colors.textLowEmphasis
174-
)
175-
}
176-
}
177-
)
178-
}
179-
)
180-
}
181-
18235
// 3 - Create an intent to start this Activity, with a given channelId
18336
companion object {
18437
private const val KEY_CHANNEL_ID = "channelId"
@@ -189,4 +42,4 @@ class MessagesActivity : AppCompatActivity() {
18942
}
19043
}
19144
}
192-
}
45+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.example.chattutorial
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import androidx.activity.compose.setContent
7+
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.compose.foundation.shape.RoundedCornerShape
9+
import androidx.compose.ui.graphics.RectangleShape
10+
import androidx.compose.ui.unit.dp
11+
import io.getstream.chat.android.compose.ui.messages.MessagesScreen
12+
import io.getstream.chat.android.compose.ui.theme.ChatTheme
13+
import io.getstream.chat.android.compose.ui.theme.StreamShapes
14+
15+
class MessagesActivity2 : AppCompatActivity() {
16+
17+
override fun onCreate(savedInstanceState: Bundle?) {
18+
super.onCreate(savedInstanceState)
19+
// 1 - Load the ID of the selected channel
20+
val channelId = intent.getStringExtra(KEY_CHANNEL_ID)
21+
22+
if (channelId == null) {
23+
finish()
24+
return
25+
}
26+
27+
// 2 - Add the MessagesScreen to your UI
28+
setContent {
29+
ChatTheme(
30+
shapes = StreamShapes( // Customizing the shapes
31+
avatar = RoundedCornerShape(8.dp),
32+
attachment = RoundedCornerShape(16.dp),
33+
myMessageBubble = RoundedCornerShape(16.dp),
34+
otherMessageBubble = RoundedCornerShape(16.dp),
35+
inputField = RectangleShape
36+
)
37+
) {
38+
MessagesScreen(
39+
channelId = channelId,
40+
messageLimit = 30,
41+
onBackPressed = { finish() }
42+
)
43+
}
44+
}
45+
}
46+
47+
// 3 - Create an intent to start this Activity, with a given channelId
48+
companion object {
49+
private const val KEY_CHANNEL_ID = "channelId"
50+
51+
fun getIntent(context: Context, channelId: String): Intent {
52+
return Intent(context, MessagesActivity2::class.java).apply {
53+
putExtra(KEY_CHANNEL_ID, channelId)
54+
}
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)