Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PBE-3749] ThreadList improvements #5455

Draft
wants to merge 67 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
b83a7bc
[PBE-3749] Update ThreadsApi to match the definition.
Oct 9, 2024
9ac769b
[PBE-3749] Register "notification.thread_message_new" EventType.
Oct 10, 2024
c5187ae
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 11, 2024
e338150
[PBE-3749] Implement initial state-management for 'Query Threads'.
Oct 11, 2024
ce4ab6d
[PBE-3749] Implement ThreadList component.
Oct 14, 2024
8d30835
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 14, 2024
fdb1ac1
[PBE-3749] Implement 'Threads' tab in compose sample app.
Oct 14, 2024
13400dc
[PBE-3749] FIx pagination logic and add a threshold.
Oct 14, 2024
7a44105
[PBE-3749] Add queryThreads preconditions checks.
Oct 14, 2024
7ccf42b
[PBE-3749] Revert ktlint commit.
Oct 14, 2024
7bf4d3f
[PBE-3749] Remove redundant state update in ThreadListController.
Oct 14, 2024
c058190
[PBE-3749] Add handling for different ChatEvents.
Oct 15, 2024
8486cde
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 15, 2024
361e754
[PBE-3749] Remove redundant coroutine creation and docs.
Oct 15, 2024
2e8c1b1
[PBE-3749] Fix detekt and spotless.
Oct 15, 2024
fbc9b3d
Revert "[PBE-3749] Implement 'Threads' tab in compose sample app."
Oct 15, 2024
cd01294
Revert "Revert "[PBE-3749] Implement 'Threads' tab in compose sample …
Oct 15, 2024
b856073
Revert "[PBE-3749] Implement 'Threads' tab in compose sample app."
Oct 15, 2024
76e5f54
[PBE-3749] Hide threads-related public apis.
Oct 15, 2024
6fc64d8
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 17, 2024
b88eca7
[PBE-3749] Fix PR remarks related DTOs.
Oct 17, 2024
b91a89c
[PBE-3749] Fix wrong composable preview.
Oct 17, 2024
3560186
[PBE-3749] Use inheritScope to create ThreadListController coroutine …
Oct 17, 2024
de906ea
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 17, 2024
72d3689
[PBE-3749] Implement ChatClient::markThreadRead operation.
Oct 18, 2024
10e5c9d
[PBE-3749] Update CHANGELOG for markThreadRead.
Oct 18, 2024
93f8f39
[PBE-3749] Fix failing test.
Oct 18, 2024
0b7d384
[PBE-3749] Separate `markThreadRead` from `markRead`.
Oct 18, 2024
321907f
[PBE-3749] Implement unreadThreads logic as part of the GlobalState.
Oct 21, 2024
47e7849
Merge branch 'refs/heads/develop' into feature/threads_v2_global_unre…
Oct 21, 2024
41558f7
[PBE-3749] Add GlobalState::unreadThreadsCount to CHANGELOG.md.
Oct 21, 2024
b93459e
Merge branch 'develop' into feature/threads_v2_mark_thread_as_read
VelikovPetar Oct 21, 2024
71ed93c
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 21, 2024
c3405d3
Merge branch 'refs/heads/feature/threads_v2_global_unread_threads_sta…
Oct 22, 2024
fe5a711
Merge branch 'refs/heads/feature/threads_v2_mark_thread_as_read' into…
Oct 22, 2024
97c93f6
[PBE-3749] Add marking thread as read handling.
Oct 22, 2024
f1fb53a
Merge branch 'refs/heads/develop' into feature/threads_v2_improvements
Oct 22, 2024
55ad64a
Merge branch 'refs/heads/develop' into feature/threads_v2_mark_thread…
Oct 22, 2024
60204d5
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 22, 2024
23a3f18
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 22, 2024
a21abaf
Merge branch 'refs/heads/feature/threads_v2_mark_thread_as_read' into…
Oct 22, 2024
49fe477
Merge branch 'refs/heads/develop' into feature/threads_v2_improvements
Oct 22, 2024
12ede31
[PBE-3749] Fix incrementing unread count for new thread messages.
Oct 22, 2024
0b9397a
[PBE-3749] Add ThreadItem customization options.
Oct 22, 2024
0b946f3
[PBE-3749] Make Threads API public.
Oct 22, 2024
5d4de4c
[PBE-3749] Add Threads tab to compose sample app.
Oct 22, 2024
27c308e
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 22, 2024
520862d
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 22, 2024
b66b1e4
Merge branch 'refs/heads/develop' into feature/threads_v2_improvements
Oct 23, 2024
ecbb061
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 23, 2024
7e3f502
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 23, 2024
86979c1
Merge branch 'refs/heads/develop' into feature/threads_v2_improvements
Oct 23, 2024
266550d
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 23, 2024
18854bc
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 23, 2024
4d89262
[PBE-3749] Add threads state tests.
Oct 23, 2024
2ce1a14
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 24, 2024
b7d3dcc
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 24, 2024
f653f97
Merge branch 'refs/heads/develop' into feature/threads_v2
Oct 25, 2024
10278c6
[PBE-3749] Suppress LongMethod warning.
Oct 25, 2024
e72c974
Merge branch 'refs/heads/feature/threads_v2' into feature/threads_v2_…
Oct 25, 2024
237ca87
[PBE-3749] Add ChatClient::markThreadUnread.
Oct 25, 2024
7123e24
[PBE-3749] Add ChatClient::markThreadUnread to CHANGELOG.md.
Oct 28, 2024
1730bed
Merge branch 'refs/heads/feature/threads_v2_mark_thread_as_unread' in…
Oct 29, 2024
212b895
[PBE-3749] Add stateless ThreadList.
Oct 30, 2024
fcfd281
[PBE-3749] Add ThreadList to CHANGELOG and add docusaurus documentation
Oct 30, 2024
88a1f05
[PBE-3749] Ensure threads state is updated on different client operat…
Oct 31, 2024
ecae6d1
[PBE-3749] Fix failing tests.
Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
### ⬆️ Improved

### ✅ Added
- Add `ChatClient::markThreadUnread` to mark a given thread as unread. [#5457](https://github.com/GetStream/stream-chat-android/pull/5457)
- Add `ChannelClient::markThreadUnread` to mark a given thread as unread. [#5457](https://github.com/GetStream/stream-chat-android/pull/5457)

### ⚠️ Changed

Expand Down Expand Up @@ -71,6 +73,7 @@
### ⬆️ Improved

### ✅ Added
- Added `ThreadList` component for showing the list of threads for the user.

### ⚠️ Changed

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
241 changes: 241 additions & 0 deletions docusaurus/docs/Android/compose/thread-components/thread-list.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# Thread List

`ThreadList` is a composable component which shows an overview of all threads of which the user is a member of.
It shows information about the channel, the thread parent message, the most recent reply in the thread, and the number of unread replies.

The component is paginated by default, and only the most recently updated threads are loaded initially. Older threads are loaded only when the user scrolls to the end of the thread list.

While this component is visible, and a new thread is created, or a thread which is not yet loaded is updated, the component will show a banner informing the user about the number of new threads, which the user can then click, to reload the thread list and load the newly updated threads.

## Usage

This component is backed by its `ThreadListViewModel`. To instantiate such `ViewModel`, you need to
first create an instance of `ThreadsViewModelFactory`:

```kotlin
private val threadsViewModelFactory by lazy {
ThreadsViewModelFactory(
threadLimit = /* ... */,
threadReplyLimit = /* ... */,
threadParticipantLimit = /* ... */
)
}
```
The `ThreadsViewModelFactory` accepts three configurable parameters:
* `threadLimit` - The maximum number of threads to be loaded per page (default: `25`).
* `threadReplyLimit` - The maximum number of (latest) replies to be loaded per thread (default: `10`).
* `threadParticipantLimit` - The maximum number of participants to be loaded per thread (default: `10`).

After the `ThreadsViewModelFactory` is configured, you can instantiate the `ThreadListViewModel` which then can be used to invoke the `ThreadList` composable:

```kotlin
private val viewModel: ThreadListViewModel by viewModels { threadsViewModelFactory }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ChatTheme {
ThreadList(
viewModel = viewModel,
modifier = Modifier.fillMaxSize(),
)
}
}
}
```

This snippet will produce a fully working thread list, together with a loading state, and an empty state for the case without threads:

| No threads | Thread list + new threads banner |
| --- | --- |
|![Empty](../../assets/compose_default_thread_list_empty.png)|![Loaded](../../assets/compose_default_thread_list_content.png)|

Alternatively, you can use the stateless version of the `ThreadList` component, which is backed by a
state object `ThreadListState`.

```kotlin
private val viewModel: ThreadListViewModel by viewModels { threadsViewModelFactory }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ChatTheme {
val state by viewModel.state.collectAsStateWithLifecycle()
ThreadList(
state = state,
onThreadClick = {
// Handle thread clicks
},
onUnreadThreadsBannerClick = {
// Handle banner clicks
},
onLoadMore = {
// Handle load more
}
)
}
}
}
```

## Handling actions

The `ThreadList` component exposes several action handlers:

```kotlin
@Composable
public fun ThreadList(
viewModel: ThreadListViewModel,
onUnreadThreadsBannerClick: () -> Unit = { viewModel.load() },
onThreadClick: (Thread) -> Unit = {},
onLoadMore: () -> Unit = { viewModel.loadNextPage() },
)
```

* `onUnreadThreadsBannerClick` - The action to be performed when a user taps on the unread threads banner.
By default, tapping on the banner results in reloading the thread list by calling `ThreadListViewModel.load`, in order to fetch the newly created threads.
You can override this method to provide a custom handling of banner clicks.
* `onThreadClick` - The action to be performed when a user taps on a thread item from the list.
By default, this handler is empty, and you can override it to provide a custom handling of the click.
The lambda provides you a `Thread` instance, which you can use to retrieve any data related the clicked thread.
* `onLoadMore` - The action to be performed when the user scrolls to the bottom of the currently loaded thread list,
and a new page of threads should be loaded. By default, this handler loads the next page of threads by calling `ThreadListViewModel.loadNextPage`,
but you can override it to provide custom behaviour.

:::note
If you are using the stateless version of the `ThreadList`, you must handle the `onUnreadThreadsBannerClick` and
`onLoadMore` actions by yourself, as the component doesn't provide a default implementation.
:::

## Customization

The `ThreadList` component allows customization of the following UI components:

```kotlin
public fun ThreadList(
modifier: Modifier = Modifier,
unreadThreadsBanner: @Composable (Int) -> Unit = { ... },
itemContent: @Composable (Thread) -> Unit = { ... },
emptyContent: @Composable () -> Unit = { ... },
loadingContent: @Composable () -> Unit = { ... },
loadingMoreContent: @Composable () -> Unit = { ... },
)
```

* `modifier` - The modifier for the root component. Used for general customization such as size and padding.
* `unreadThreadsBanner` - Composable that represents the banner shown when a new thread is created.
By default, it shows the number of updated threads which are not loaded, and provides a refresh button
which reloads the thread list. The lambda provides an `Int` argument, which represents the number of new/updated threads which are not loaded.
You can use this argument to show the correct number of new/updated threads when overriding this component.
* `itemContent` - Composable that represents a single thread item in the list. This composable is used
to render each item in the list. Override this to provide a customized design of the thread item.
It provides a `Thread` argument, which represents the thread instance for which the item is rendered.
* `emptyContent` - Composable that represents the content shown when there are no threads. Override this to provide a custom empty content.
* `loadingContent` - Composable that represents the content shown during the initial loading of the threads. Override this to provide a custom loading content.
* `loadingMoreContent` - Composable that represents the section shown below the items during the loading of the next batch (page) of threads. Override to provide a custom loading more indicator.

The most commonly customized component is the `itemContent`. Therefore, there are additional customization options for this component.
The base component for this is the `ThreadItem`:

```kotlin
@Composable
public fun ThreadItem(
thread: Thread,
currentUser: User?,
onThreadClick: (Thread) -> Unit,
modifier: Modifier = Modifier,
titleContent: @Composable (Channel) -> Unit = { ... },
replyToContent: @Composable RowScope.(parentMessage: Message) -> Unit = { ... },
unreadCountContent: @Composable RowScope.(unreadCount: Int) -> Unit = { ... },
latestReplyContent: @Composable (reply: Message) -> Unit = { ... },
)
```

* `modifier` - The modifier for the root component. You can apply a background, elevation, padding...
* `titleContent` - Composable that represents the title of the thread item. By default, it shows a
thread icon and the channel name in which the thread exists. The lambda provides a `Channel` instance,
which you can utilize to create a custom thread title component.
* `replyToContent` - Composable that represents the parent message of the thread. By default, it shows
a preview of the parent message, with a `replied to:` prefix. The lambda provides a `Message` object,
which represents the parent message of the thread. Override this lambda to provide a custom component
showing the parent message of the thread.
* `unreadCountContent` - Composable representing the unread message count indicator. By default, it
shows a red circle with the number of unread thread messages. The lambda provides an `unreadCount` argument,
which you can use to create a custom unread count indicator.
* `latestReplyContent` - Composable representing the part of the item where the latest thread reply
is rendered. By default, it shows a preview of the latest message in the thread, including the name
and avatar of the user who wrote the message, and a timestamp when the message was created. The lambda
provides a `Message` object, which represents the latest reply in the thread. You can override this
lambda to provide a custom representation of the latest thread reply.

For example, if you would like to override the title and the latest reply content of the thread items,
you can do:

```kotlin
ThreadList(
viewModel = viewModel,
itemContent = { thread ->
ThreadItem(
thread = thread,
currentUser = currentUser
titleContent = { channel ->
// Custom thread title
},
latestReplyContent = { reply ->
// Custom latest reply content
},
onThreadClick = { thread ->
// Handle item click
}
)
}
)
```

If you would like to keep the same layout as the default item, but you want to show a custom text for
the title, you can also utilize the global channel name formatter `ChannelNameFormatter`, which is
used to format the thread title as well (by default, the thread title shows the channel name).

```kotlin
class CustomChannelNameFormatter: ChannelNameFormatter {
override fun formatChannelName(channel: Channel, currentUser: User?): String {
// custom channel name formatting
}
}

// Usage
@Composable
public fun YourScreen() {
ChatTheme(
channelNameFormatter = CustomChannelNameFormatter() // override the default formatter
) {
ThreadList(viewModel = viewModel)
}
}
```

Similarly, you can customize the message preview formatting for the parent message and the reply message,
by overriding the global message preview formatter `MessagePreviewFormatter`:

```kotlin
class CustomMessagePreviewFormatter: MessagePreviewFormatter {

// Other methods

override fun formatMessagePreview(message: Message, currentUser: User?): AnnotatedString {
// Your implementation for customized message text
}
}

// Usage
@Composable
public fun YourScreen() {
ChatTheme(
messagePreviewFormatter = CustomMessagePreviewFormatter() // override the default formatter
) {
ThreadList(viewModel = viewModel)
}
}
```
1 change: 1 addition & 0 deletions docusaurus/sidebars-android.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ module.exports = {
"compose/general-customization/attachment-factory",
],
},
"compose/thread-components/thread-list",
{
"Utility Components": [
"compose/utility-components/user-avatar",
Expand Down
Loading
Loading