Introduce ThreadsScreen and ThreadListHeader components#6376
Conversation
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThe pull request introduces a new Changes
Sequence DiagramsequenceDiagram
actor User as User
participant Activity as ChannelsActivity
participant Screen as ThreadsScreen
participant VM as ThreadListViewModel
participant Header as ThreadListHeader
participant List as ThreadList
participant Controller as ThreadListController
participant ChatClient as ChatClient
User->>Activity: Click THREADS tab
Activity->>Screen: Render ThreadsScreen(factory, title, onHeaderAvatarClick)
Screen->>VM: Create via ThreadsViewModelFactory
Screen->>VM: collectAsState(user)
Screen->>VM: collectAsState(connectionState)
VM->>Controller: Access user & connectionState StateFlows
Controller->>ChatClient: Get clientState.user & connectionState
ChatClient-->>Controller: Emit user & connectionState
Controller-->>VM: Provide StateFlows
VM-->>Screen: Emit user & connectionState states
Screen->>Header: Render ThreadListHeader(title, user, connectionState, onAvatarClick)
Header->>Header: Show avatar (if user exists) or spacer
Header->>Header: Show title (connected), loading indicator (connecting), or "Disconnected"
Screen->>List: Render ThreadList(viewModel, onThreadClick)
List->>VM: Access thread state
VM->>Controller: Load threads via QueryThreadsRequest
Controller-->>VM: Emit thread list
VM-->>List: Provide threads state
List-->>Screen: Render thread items
User->>Header: Click avatar
Header->>Activity: onHeaderAvatarClick()
Activity->>Activity: drawerState.open()
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/ListHeader.kt (1)
149-176: Optional: deduplicate connected/offline Text branches.The
ConnectedandOfflinearms render the sameTextwith identical modifier/style, only the text differs. A tiny local helper or computingtextfirst would reduce duplication.♻️ Suggested refactor
- when (connectionState) { - is ConnectionState.Connected -> { - Text( - modifier = Modifier - .weight(1f) - .wrapContentWidth() - .padding(horizontal = StreamTokens.spacingMd), - text = title, - style = ChatTheme.typography.headingSmall, - maxLines = 1, - color = ChatTheme.colors.textPrimary, - ) - } - - is ConnectionState.Connecting -> NetworkLoadingIndicator(modifier = Modifier.weight(1f)) - is ConnectionState.Offline -> { - Text( - modifier = Modifier - .weight(1f) - .wrapContentWidth() - .padding(horizontal = StreamTokens.spacingMd), - text = stringResource(R.string.stream_compose_disconnected), - style = ChatTheme.typography.headingSmall, - maxLines = 1, - color = ChatTheme.colors.textPrimary, - ) - } - } + when (connectionState) { + is ConnectionState.Connecting -> NetworkLoadingIndicator(modifier = Modifier.weight(1f)) + is ConnectionState.Connected, + is ConnectionState.Offline, + -> Text( + modifier = Modifier + .weight(1f) + .wrapContentWidth() + .padding(horizontal = StreamTokens.spacingMd), + text = if (connectionState is ConnectionState.Connected) { + title + } else { + stringResource(R.string.stream_compose_disconnected) + }, + style = ChatTheme.typography.headingSmall, + maxLines = 1, + color = ChatTheme.colors.textPrimary, + ) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/ListHeader.kt` around lines 149 - 176, The Connected and Offline branches in ListHeader.kt duplicate the same Text composable; compute the displayed string first (e.g., val headerText = when (connectionState) { is ConnectionState.Connected -> title; is ConnectionState.Offline -> stringResource(R.string.stream_compose_disconnected); else -> null }) or create a small helper composable, then render a single Text with the existing Modifier (.weight(1f).wrapContentWidth().padding(StreamTokens.spacingMd)), style (ChatTheme.typography.headingSmall) and color (ChatTheme.colors.textPrimary); keep the NetworkLoadingIndicator branch (ConnectionState.Connecting) as-is.stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt (1)
67-69: Consider exposing a customizable trailing slot.The trailing area is hardcoded to a
Spacersized toAvatarSize.ExtraLargeto balance the leading avatar so the centered title remains centered. That works for the default case, but it prevents consumers from customizing the trailing slot (e.g., adding a settings/search action) without replacing the entire composable — unlikeChannelListHeaderwhich exposesleadingContent/centerContent/trailingContentparameters. If future customization is anticipated, mirroring that slot pattern (with theSpaceras the default) would be more consistent and extensible.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt` around lines 67 - 69, ThreadListHeader currently hardcodes trailingContent to a Spacer sized AvatarSize.ExtraLarge which prevents consumers from adding custom trailing UI; change ThreadListHeader signature to accept an optional trailingContent composable parameter (mirror ChannelListHeader's leadingContent/centerContent/trailingContent pattern) and use the provided trailingContent when non-null, falling back to the existing Spacer(Modifier.size(AvatarSize.ExtraLarge)) default; update the call sites and the composable parameter list (ThreadListHeader, trailingContent) so existing behavior is preserved but consumers can pass custom content.stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt (1)
57-72: PreferModifier.weight(1f)onThreadListinside the Column.Using
Modifier.fillMaxSize()on a child inside a verticalColumnis fragile: it works today because the header measures first and the remaining max height is passed down, but it disables intrinsic sizing and can cause layout oddities when the parent has unbounded height (e.g., inside a scrollable container). The idiomatic pattern isModifier.weight(1f)for the flexible child so it takes the remaining space after the header. Also consider exposing amodifier: Modifier = Modifierparameter onThreadsScreenfor consistency with other screen composables and to allow callers to apply padding/insets.♻️ Proposed change
`@Composable` public fun ThreadsScreen( + modifier: Modifier = Modifier, viewModelFactory: ThreadsViewModelFactory = ThreadsViewModelFactory(query = QueryThreadsRequest()), title: String = stringResource(R.string.stream_compose_thread_list_header_title), onHeaderAvatarClick: () -> Unit = {}, onThreadClick: (Thread) -> Unit = {}, ) { val listViewModel: ThreadListViewModel = viewModel(factory = viewModelFactory) val user by listViewModel.user.collectAsState() val connectionState by listViewModel.connectionState.collectAsState() - Column(modifier = Modifier.fillMaxSize()) { + Column(modifier = modifier.fillMaxSize()) { ChatTheme.componentFactory.ThreadListHeader( params = ThreadListHeaderParams( title = title, currentUser = user, connectionState = connectionState, onAvatarClick = { onHeaderAvatarClick() }, ), ) ThreadList( viewModel = listViewModel, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.weight(1f).fillMaxWidth(), onThreadClick = onThreadClick, ) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt` around lines 57 - 72, The ThreadList child currently uses Modifier.fillMaxSize() inside the Column which is fragile; update ThreadsScreen so ThreadList uses Modifier.weight(1f) (e.g., ThreadList(modifier = Modifier.weight(1f), ...)) so it correctly takes remaining space after ChatTheme.componentFactory.ThreadListHeader, and also add an optional parameter modifier: Modifier = Modifier to ThreadsScreen and thread that through the Column (Column(modifier = modifier.fillMaxSize())) to allow callers to apply padding/insets consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/ListHeader.kt`:
- Around line 149-176: The Connected and Offline branches in ListHeader.kt
duplicate the same Text composable; compute the displayed string first (e.g.,
val headerText = when (connectionState) { is ConnectionState.Connected -> title;
is ConnectionState.Offline ->
stringResource(R.string.stream_compose_disconnected); else -> null }) or create
a small helper composable, then render a single Text with the existing Modifier
(.weight(1f).wrapContentWidth().padding(StreamTokens.spacingMd)), style
(ChatTheme.typography.headingSmall) and color (ChatTheme.colors.textPrimary);
keep the NetworkLoadingIndicator branch (ConnectionState.Connecting) as-is.
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt`:
- Around line 67-69: ThreadListHeader currently hardcodes trailingContent to a
Spacer sized AvatarSize.ExtraLarge which prevents consumers from adding custom
trailing UI; change ThreadListHeader signature to accept an optional
trailingContent composable parameter (mirror ChannelListHeader's
leadingContent/centerContent/trailingContent pattern) and use the provided
trailingContent when non-null, falling back to the existing
Spacer(Modifier.size(AvatarSize.ExtraLarge)) default; update the call sites and
the composable parameter list (ThreadListHeader, trailingContent) so existing
behavior is preserved but consumers can pass custom content.
In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt`:
- Around line 57-72: The ThreadList child currently uses Modifier.fillMaxSize()
inside the Column which is fragile; update ThreadsScreen so ThreadList uses
Modifier.weight(1f) (e.g., ThreadList(modifier = Modifier.weight(1f), ...)) so
it correctly takes remaining space after
ChatTheme.componentFactory.ThreadListHeader, and also add an optional parameter
modifier: Modifier = Modifier to ThreadsScreen and thread that through the
Column (Column(modifier = modifier.fillMaxSize())) to allow callers to apply
padding/insets consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c454c58b-0a0c-4585-9772-39ff1b012487
⛔ Files ignored due to path filters (6)
stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connected,_no_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connected,_with_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connecting,_no_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connecting,_with_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_offline,_no_user.pngis excluded by!**/*.pngstream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_offline,_with_user.pngis excluded by!**/*.png
📒 Files selected for processing (13)
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.ktstream-chat-android-compose/api/stream-chat-android-compose.apistream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/header/ChannelListHeader.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/ListHeader.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/threads/ThreadListViewModel.ktstream-chat-android-compose/src/main/res/values/strings.xmlstream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/threads/ThreadListHeaderTest.ktstream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/threads/ThreadListController.ktstream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/threads/ThreadListControllerTest.kt
SDK Size Comparison 📏
|
|
|
🚀 Available in v7.0.1 |


Goal
Add a header to the thread list screen, matching the Figma specs and the existing
ChannelsScreen/ChannelListHeaderpattern.Same as #6342, which got closed when I deleted the
v7branch.Implementation
ThreadListHeadercomposable that reusesChannelListHeaderinternally with no trailing action buttonThreadsScreencomposable that wrapsThreadListHeader+ThreadList, mirroringChannelsScreenThreadListHeaderParamsandChatComponentFactory.ThreadListHeaderfor customizationuserandconnectionStateonThreadListControllerandThreadListViewModelThreadsScreen🎨 UI Changes
Testing
Launch the sample and verify that the threads screen shows the header
Summary by CodeRabbit