Skip to content

Introduce ThreadsScreen and ThreadListHeader composables#6342

Open
gpunto wants to merge 3 commits intov7from
threads-header
Open

Introduce ThreadsScreen and ThreadListHeader composables#6342
gpunto wants to merge 3 commits intov7from
threads-header

Conversation

@gpunto
Copy link
Copy Markdown
Contributor

@gpunto gpunto commented Apr 8, 2026

Goal

Add a header to the thread list screen, matching the Figma specs and the existing ChannelsScreen / ChannelListHeader pattern.

Implementation

  • Add ThreadListHeader composable that reuses ChannelListHeader internally with no trailing action button
  • Add ThreadsScreen composable that wraps ThreadListHeader + ThreadList, mirroring ChannelsScreen
  • Add ThreadListHeaderParams and ChatComponentFactory.ThreadListHeader for customization
  • Expose user and connectionState on ThreadListController and ThreadListViewModel
  • Update the sample app to use ThreadsScreen

🎨 UI Changes

Before After
Screenshot_20260408_150321 Screenshot_20260408_150207

Testing

Launch the sample and verify that the threads screen shows the header

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a dedicated thread list header component displaying connection status and current user information.
    • Introduced a unified ThreadsScreen composable integrating the header with the thread list interface.
    • Enhanced thread list presentation with real-time connection state indication and user session display.
  • Tests

    • Added snapshot tests for the thread list header across multiple connection states and user scenarios.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@gpunto gpunto added the pr:new-feature New feature label Apr 8, 2026
@gpunto gpunto changed the title Add ThreadsScreen and ThreadListHeader Introduce ThreadsScreen and ThreadListHeader composables Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.82 MB 0.57 MB 🔴
stream-chat-android-ui-components 10.60 MB 10.96 MB 0.36 MB 🟡
stream-chat-android-compose 12.81 MB 12.26 MB -0.55 MB 🚀

@gpunto gpunto marked this pull request as ready for review April 8, 2026 14:10
@gpunto gpunto requested a review from a team as a code owner April 8, 2026 14:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Walkthrough

This PR adds a new ThreadsScreen composable entry point with an accompanying ThreadListHeader component, extends the ThreadListViewModel and ThreadListController with reactive state properties for connection status and user information, introduces supporting parameter classes and factory methods, and updates the sample app to use the new screen.

Changes

Cohort / File(s) Summary
Sample Application
stream-chat-android-compose-sample/.../ChannelsActivity.kt
Updated to use new ThreadsScreen/ThreadsViewModelFactory instead of direct ThreadListViewModel binding. Added onHeaderAvatarClick callback that opens drawer in coroutine.
New Composable Components
stream-chat-android-compose/.../ThreadListHeader.kt, stream-chat-android-compose/.../ThreadsScreen.kt
Introduced ThreadListHeader that wraps ChannelListHeader with connection/user state display; introduced ThreadsScreen composable that instantiates view model, collects state, and renders header above thread list.
Factory and Component Registry
stream-chat-android-compose/.../ChatComponentFactory.kt
Added public composable factory method ThreadListHeader(params: ThreadListHeaderParams) delegating to the composable implementation.
Parameter Classes
stream-chat-android-compose/.../ChatComponentFactoryParams.kt
Added new ThreadListHeaderParams data class with connectionState, modifier, title, currentUser, and onAvatarClick callback fields.
ViewModel and Controller Extensions
stream-chat-android-compose/.../ThreadListViewModel.kt, stream-chat-android-ui-common/.../ThreadListController.kt
Extended both with public connectionState: StateFlow<ConnectionState> and user: StateFlow<User?> properties exposing client state.
API Surface
stream-chat-android-compose/api/stream-chat-android-compose.api
Added new public signatures for ThreadListHeader, ThreadsScreen, ThreadListHeaderParams, and ViewModel getters.
Tests
stream-chat-android-compose/.../ThreadListHeaderTest.kt, stream-chat-android-ui-common/.../ThreadListControllerTest.kt
Added snapshot tests for ThreadListHeader in six connection/user states; updated controller test fixture to mock ClientState.
Resources
stream-chat-android-compose/.../strings.xml
Added string resource stream_compose_thread_list_header_title with value "Threads".

Sequence Diagram

sequenceDiagram
    participant Client as Client Code
    participant TS as ThreadsScreen
    participant VM as ThreadListViewModel
    participant LC as ThreadListController
    participant CS as ClientState

    Client->>TS: Call ThreadsScreen(factory, title, callbacks)
    TS->>VM: Instantiate via ThreadsViewModelFactory
    TS->>LC: Access controller from VM
    TS->>CS: collectAsState(user)
    TS->>CS: collectAsState(connectionState)
    CS-->>TS: Emit user
    CS-->>TS: Emit connectionState
    TS->>TS: Render Column with ThreadListHeader
    TS->>TS: Pass user & connectionState to header
    TS->>TS: Render ThreadList below header
    Client->>TS: onHeaderAvatarClick triggered
    TS->>Client: Invoke onHeaderAvatarClick callback
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Threads now have a header to display,
With user and connection state on display!
StateFlows compose in harmony so true,
A ThreadsScreen emerges, polished and new.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: introducing two new composables (ThreadsScreen and ThreadListHeader) that form the core objective of this PR.
Description check ✅ Passed The PR description covers the primary template sections: Goal (adding a header to match Figma specs), Implementation (composables and exposure of state), UI Changes (before/after screenshots), and Testing (launch sample and verify header). While the Contributor Checklist is unchecked, the critical content is present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch threads-header

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt (1)

212-216: Optional cleanup: deduplicate the drawer-open callback used by CHATS and THREADS branches.
This reduces repetition and keeps tab content branches easier to scan.

♻️ Proposed refactor
-                                when (selectedTab) {
+                                val openDrawerFromHeader: () -> Unit = {
+                                    coroutineScope.launch { drawerState.open() }
+                                }
+
+                                when (selectedTab) {
                                     AppBottomBarOption.CHATS -> ChannelsContent(
-                                        onHeaderAvatarClick = {
-                                            coroutineScope.launch {
-                                                drawerState.open()
-                                            }
-                                        },
+                                        onHeaderAvatarClick = openDrawerFromHeader,
                                     )
 
                                     AppBottomBarOption.MENTIONS -> MentionsContent()
                                     AppBottomBarOption.THREADS -> ThreadsContent(
-                                        onHeaderAvatarClick = {
-                                            coroutineScope.launch {
-                                                drawerState.open()
-                                            }
-                                        },
+                                        onHeaderAvatarClick = openDrawerFromHeader,
                                     )
                                 }

Also applies to: 221-225

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt`
around lines 212 - 216, The onHeaderAvatarClick lambda is duplicated in the
CHATS and THREADS tab branches; extract it into a single reusable callback to
reduce repetition—create a val (e.g., openDrawer = { coroutineScope.launch {
drawerState.open() } }) or a private function (e.g., fun openDrawer() {
coroutineScope.launch { drawerState.open() } }) and replace the inline lambdas
used by onHeaderAvatarClick in both branches with that single reference; ensure
the new symbol is in scope where CHATS and THREADS are declared so both branches
can call it.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt (1)

35-44: Complete ThreadsScreen KDoc with thread expectations.

Please add a short note about expected threading context for state consumption/callback execution to keep the public API docs consistent with module standards.

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

🤖 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 35 - 44, Update the KDoc for ThreadsScreen to include a short note
describing threading expectations: state exposed by ThreadListViewModel is
consumed on the main (UI) thread and all callback handlers (onHeaderAvatarClick,
onThreadClick) are invoked on the main thread so callers should perform
long-running work off the UI thread. Reference the ThreadListViewModel in the
doc and add a "Threading" or "Concurrency" paragraph clarifying that UI state
and callbacks run on the main dispatcher and advising to switch contexts for
heavy work.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/threads/ThreadListViewModel.kt (1)

39-47: Add thread-expectation notes to the new public StateFlow KDoc.

The new KDoc explains state semantics, but it doesn’t document thread expectations (emission/collection context), which is required for public APIs in this module.

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

🤖 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/viewmodel/threads/ThreadListViewModel.kt`
around lines 39 - 47, Update the KDoc for the public StateFlow properties to
include thread-expectation and state notes: for both connectionState:
StateFlow<ConnectionState> and user: StateFlow<User?> (in ThreadListViewModel),
add a sentence indicating on which thread/context they emit and should be
collected (for example: "Emits on the main thread and is safe to collect from
UI/CoroutineScope tied to the main dispatcher" or the actual dispatcher used by
controller), and mention whether the StateFlow is hot/long-lived and its update
semantics (e.g., conflated/latest value). Ensure both property KDocs clearly
state emission/collection expectations and lifecycle implications per the
module's public API documentation guidelines.
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt (1)

38-50: Add thread-expectation details in ThreadListHeader public KDoc.

The API docs are clear on behavior/state; adding threading expectations would make this public composable fully aligned with the module’s KDoc contract.

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

🤖 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 38 - 50, Update the public KDoc for the ThreadListHeader composable
to include threading and state expectations: state that ThreadListHeader is a
`@Composable` UI function that must be invoked on the UI/main thread (Compose
runtime), describe which state objects are observed (e.g., connectionState,
currentUser) and that callers should pass stable/immutable state to avoid
unnecessary recompositions, and note any side-effect expectations (no
long‑running work inside the composable; perform IO in viewmodels/effects).
Reference the composable name ThreadListHeader and the observed parameters
connectionState and currentUser so reviewers can locate and verify the added
documentation.
🤖 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-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt`:
- Around line 212-216: The onHeaderAvatarClick lambda is duplicated in the CHATS
and THREADS tab branches; extract it into a single reusable callback to reduce
repetition—create a val (e.g., openDrawer = { coroutineScope.launch {
drawerState.open() } }) or a private function (e.g., fun openDrawer() {
coroutineScope.launch { drawerState.open() } }) and replace the inline lambdas
used by onHeaderAvatarClick in both branches with that single reference; ensure
the new symbol is in scope where CHATS and THREADS are declared so both branches
can call it.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt`:
- Around line 38-50: Update the public KDoc for the ThreadListHeader composable
to include threading and state expectations: state that ThreadListHeader is a
`@Composable` UI function that must be invoked on the UI/main thread (Compose
runtime), describe which state objects are observed (e.g., connectionState,
currentUser) and that callers should pass stable/immutable state to avoid
unnecessary recompositions, and note any side-effect expectations (no
long‑running work inside the composable; perform IO in viewmodels/effects).
Reference the composable name ThreadListHeader and the observed parameters
connectionState and currentUser so reviewers can locate and verify the added
documentation.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt`:
- Around line 35-44: Update the KDoc for ThreadsScreen to include a short note
describing threading expectations: state exposed by ThreadListViewModel is
consumed on the main (UI) thread and all callback handlers (onHeaderAvatarClick,
onThreadClick) are invoked on the main thread so callers should perform
long-running work off the UI thread. Reference the ThreadListViewModel in the
doc and add a "Threading" or "Concurrency" paragraph clarifying that UI state
and callbacks run on the main dispatcher and advising to switch contexts for
heavy work.

In
`@stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/threads/ThreadListViewModel.kt`:
- Around line 39-47: Update the KDoc for the public StateFlow properties to
include thread-expectation and state notes: for both connectionState:
StateFlow<ConnectionState> and user: StateFlow<User?> (in ThreadListViewModel),
add a sentence indicating on which thread/context they emit and should be
collected (for example: "Emits on the main thread and is safe to collect from
UI/CoroutineScope tied to the main dispatcher" or the actual dispatcher used by
controller), and mention whether the StateFlow is hot/long-lived and its update
semantics (e.g., conflated/latest value). Ensure both property KDocs clearly
state emission/collection expectations and lifecycle implications per the
module's public API documentation guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 59daf2e4-d893-4987-a605-ca3dfdd96ab2

📥 Commits

Reviewing files that changed from the base of the PR and between ec339bf and 8ab52d0.

⛔ 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.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connected,_with_user.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connecting,_no_user.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_connecting,_with_user.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_offline,_no_user.png is excluded by !**/*.png
  • stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.threads_ThreadListHeaderTest_offline,_with_user.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt
  • stream-chat-android-compose/api/stream-chat-android-compose.api
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactoryParams.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadListHeader.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/threads/ThreadsScreen.kt
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/threads/ThreadListViewModel.kt
  • stream-chat-android-compose/src/main/res/values/strings.xml
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/threads/ThreadListHeaderTest.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/threads/ThreadListController.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/threads/ThreadListControllerTest.kt

Copy link
Copy Markdown
Contributor

@VelikovPetar VelikovPetar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me, left just one small suggestion - feel free to ignore if you think it doesn't make sense.

If it is decided to introduce the header/screen as SDK components, let's merge this PR.

elevation: Dp = 0.dp,
onAvatarClick: (User?) -> Unit = {},
) {
ChannelListHeader(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a small suggestion: Instead of calling ChannelListHeader from the ThreadListHeader, maybe we can have a common ListHeader (or something similar) that will be called by both the channels and the threads header?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! I extracted a common ListHeader & related components so now Channels/Threads implementations point to those

@gpunto
Copy link
Copy Markdown
Contributor Author

gpunto commented Apr 9, 2026

Overall looks good to me, left just one small suggestion - feel free to ignore if you think it doesn't make sense.

If it is decided to introduce the header/screen as SDK components, let's merge this PR.

So, the implementation is not consistent across mobile SDKs, but the code currently in the PR seems to be aligned with what iOS exposes (and asked in Slack):

Header: exposed via factory (ThreadListHeader / makeThreadListHeaderViewModifier)
Screen: public composable/view, not in the factory (ThreadsScreen / ChatThreadListView)

WDYT?

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
60.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants