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

[i233] add avatar user/channel renderer #5141

Merged
merged 5 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ The `AvatarView` will use the default gradient color with initials if the `image


#### Customizing Avatars Using Styles
You can configure the avatar shape, border width, online indicator and other aspects using [AvatarStyle](https://github.com/GetStream/stream-chat-android/blob/main/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/avatar/AvatarStyle.kt). You can create this kind of avatar by changing the shape and corner radius:
You can configure the avatar shape, border width, online indicator and other aspects using [AvatarStyle](https://github.com/GetStream/stream-chat-android/blob/v5/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/avatar/AvatarStyle.kt). You can create this kind of avatar by changing the shape and corner radius:

| Light Mode | Dark Mode |
|---|---|
Expand Down
181 changes: 181 additions & 0 deletions docusaurus/docs/Android/04-ui/02-general-customization/02-chatui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,187 @@ ChatUI.setMimeTypeIconProvider(mimeType -> {
</TabItem>
</Tabs>

## Customizing Avatars
An avatar is a small image which identifies a specific user or channel.
`UserAvatarView` is used in the lists of users and messages, whereas `ChannelAvatarView` is used in the lists of channels.

The image in the `UserAvatarView` and `ChannelAvatarView` is being displayed based on the `image` property, present in both `User` and `Channel` objects respectively:

| Light Mode | Dark Mode |
|---|---|
| ![Default Avatar Light Mode](../../assets/default_channel.png) | ![Default Avatar Dark Mode](../../assets/default_channel_dark.png) |

Both `UserAvatarView` and `ChannelAvatarView` will use the default gradient color with initials if the `image` property cannot be loaded:

| Light Mode | Dark Mode |
|---|---|
| ![Default Avatar Light Mode](../../assets/default_avatar.png) | ![Default Avatar Dark Mode](../../assets/default_avatar_dark.png) |

In addition, `ChannelAvatarView` provides several extra fallback scenarios if `image` property is an empty string:
- If the channel has just `1` member, the `image` property of this user will be used to display an avatar.
- If the channel has just `2` members, the `name` property of the user, who is not the current user will be used to display an avatar.
- If the channel has more than two members, the avatar image will be produced from the first `4` users in the channel's member list.

#### Customizing Avatars Using Styles
You can configure the avatar shape, border width, online indicator and other aspects using [AvatarStyle](https://github.com/GetStream/stream-chat-android/blob/main/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/widgets/avatar/AvatarStyle.kt). You can create this kind of avatar by changing the shape and corner radius:

| Light Mode | Dark Mode |
|---|---|
| ![Light Mode](../../assets/square_avatar.png) | ![Dark Mode](../../assets/square_avatar_dark.png)

#### Customizing User Avatars Using UserAvatarRenderer

Overriding the `UserAvatarRenderer` allows you to add custom logic for the and user's avatars displayed using `UserAvatarView`.

:::note
The `UserAvatarView.setAvatar` method mentioned below can accept different data types as `avatar` parameter.
It can be a `Bitmap`, `Drawable`, `Uri`, `String` or `DrawbleRes` (resource id).
:::

<Tabs>
<TabItem value="kotlin" label="Kotlin">

```kotlin
object : UserAvatarRenderer {
override fun render(style: AvatarStyle, user: User, target: UserAvatarView) {
// You can apply custom image loading logic here
val placeholder: Drawable = /* ... */
target.setAvatar(avatar = user.image, placeholder = placeholder)
target.setOnline(online = user.online)
}
}
```
</TabItem>

<TabItem value="java" label="Java">

```java
new UserAvatarRenderer() {
@Override
public void render(
@NonNull AvatarStyle style,
@NonNull User user,
@NonNull UserAvatarView target
) {
// You can apply custom image loading logic here
final Drawable placeholder = /* ... */;
target.setAvatar(user.getImage(), placeholder);
target.setOnline(user.getOnline());
}
};
```
</TabItem>
</Tabs>

#### Customizing Channel Avatars Using ChannelAvatarRenderer

Overriding the `ChannelAvatarRenderer` allows you to add custom logic for the channel's avatars displayed using `ChannelAvatarView`.

:::note
The `AvatarImageView.setAvatar` method mentioned below can accept different data types as `avatar` parameter.
It can be a `Bitmap`, `Drawable`, `Uri`, `String` or `DrawbleRes` (resource id).
:::

Let's look at the different scenarios for channel avatars, which are handled by the default.
You can override the `ChannelAvatarRenderer` to apply custom logic for each scenario or just simplify the logic to a single approach for all scenarios.

<Tabs>
<TabItem value="kotlin" label="Kotlin">

```kotlin
object : ChannelAvatarRenderer {
override fun render(
style: AvatarStyle,
channel: Channel,
user: User,
targetProvider: ChannelAvatarViewProvider,
) {
// You can apply custom image loading logic here
val placeholder: Drawable = /* ... */

// Scenario_1: `Channel.image` is not empty
val target1: AvatarImageView = targetProvider.regular()
target1.setAvatar(avatar = channel.image, placeholder = placeholder)

// Scenario_2: `Channel.image` is empty and `Channel` has less or equal to 2 members
val singleUser: User = /* ... */
val target2: UserAvatarView = targetProvider.singleUser()
target2.setAvatar(avatar = singleUser.image, placeholder = placeholder)
target2.setOnline(online = singleUser.online)

// Scenario_3: `Channel.image` is empty and `Channel` has more than 2 members
val users = channel.members.filter { it.user.id != currentUser?.id }.map { it.user }
val target3: List<AvatarImageView> = targetProvider.userGroup(users.size)
target3.forEachIndexed { index, targetItem ->
targetItem.setAvatar(avatar = users[index].image, placeholder = placeholder)
}
}
}
```
</TabItem>

<TabItem value="java" label="Java">

```java
new ChannelAvatarRenderer() {
@Override
public void render(
@NonNull AvatarStyle style,
@NonNull User user,
@NonNull UserAvatarView target
) {
// You can apply custom image loading logic here
final Drawable placeholder = /* ... */;

// Scenario_1: `Channel.image` is not empty
final AvatarImageView target1 = targetProvider.regular();
target1.setAvatar(channel.getImage(), placeholder);

// Scenario_2: `Channel.image` is empty and `Channel` has less or equal to 2 members
final User singleUser = /* ... */;
final UserAvatarView target2 = targetProvider.singleUser();
target2.setAvatar(singleUser.getImage(), placeholder);
target2.setOnline(singleUser.getOnline());

// Scenario_3: `Channel.image` is empty and `Channel` has more than 2 members
final List<User> users = channel.getMembers().stream()
.filter(member -> !member.getUser().getId().equals(currentUser.getId()))
.map(Member::getUser)
.collect(Collectors.toList());
final List<AvatarImageView> target3 = targetProvider.userGroup(users.size());
for (int i = 0; i < target3.size(); i++) {
target3.get(i).setAvatar(users.get(i).getImage(), placeholder);
}
}
};
```
</TabItem>
</Tabs>



**If you only would like to change the gradient colors for the default avatar**, you can use `stream_ui_avatar_gradient_colors`.

The default color set includes a variety of colors:

| Light Mode | Dark Mode |
|---|---|
| ![Colorful Avatars Light Mode](../../assets/colorful_avatars.png) | ![Colorful Avatars Dark Mode](../../assets/colorful_avatars_dark.png) |

The set can be overridden in the `color.xml` file - you can expand or reduce the number of supported colors, like in the example below:

```
<array name="stream_ui_avatar_gradient_colors">
<item>@color/stream_ui_avatar_gradient_blue</item>
</array>
```

Which creates:

| Light Mode | Dark Mode |
|---|---|
| ![Blue Gradient Avatars Light Mode](../../assets/blue_gradients_avatars.png) | ![Blue Gradient Avatars Dark Mode](../../assets/blue_gradients_avatars_dark.png) |

## Adding Extra Headers to Image Requests

If you're [using your own CDN](https://getstream.io/chat/docs/android/file_uploads/?language=kotlin#using-your-own-cdn), you might also need to add extra headers to image loading requests. You can do this by creating your own implementation of the `ImageHeadersProvider` interface and then setting it on `ChatUI`:
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.
Binary file modified docusaurus/docs/Android/assets/colorful_avatars.png
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.
Binary file modified docusaurus/docs/Android/assets/default_avatar.png
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.
Binary file modified docusaurus/docs/Android/assets/default_channel.png
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.
Binary file modified docusaurus/docs/Android/assets/square_avatar.png
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.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
Expand All @@ -16,10 +19,15 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import io.getstream.chat.android.markdown.MarkdownTextTransformer;
import io.getstream.chat.android.models.Channel;
import io.getstream.chat.android.models.User;
import io.getstream.chat.android.models.Member;
import io.getstream.chat.android.ui.ChatUI;
import io.getstream.chat.android.ui.common.helper.DateFormatter;
import io.getstream.chat.android.ui.feature.messages.composer.attachment.preview.AttachmentPreviewFactoryManager;
Expand All @@ -30,6 +38,12 @@
import io.getstream.chat.android.ui.helper.SupportedReactions;
import io.getstream.chat.android.ui.navigation.ChatNavigationHandler;
import io.getstream.chat.android.ui.navigation.ChatNavigator;
import io.getstream.chat.android.ui.widgets.avatar.AvatarImageView;
import io.getstream.chat.android.ui.widgets.avatar.AvatarStyle;
import io.getstream.chat.android.ui.widgets.avatar.ChannelAvatarRenderer;
import io.getstream.chat.android.ui.widgets.avatar.ChannelAvatarViewProvider;
import io.getstream.chat.android.ui.widgets.avatar.UserAvatarRenderer;
import io.getstream.chat.android.ui.widgets.avatar.UserAvatarView;
import io.getstream.chat.docs.R;

/**
Expand Down Expand Up @@ -226,6 +240,57 @@ private void customizeQuotedMessageContent() {
* [Disabling Video Thumbnails](https://getstream.io/chat/docs/sdk/android/ui/general-customization/chatui/#disabling-video-thumbnails)
*/
private void disablingVideoThumbnails(){
ChatUI.setVideoThumbnailsEnabled(false);

}

private void customizingUserAvatarRenderer() {
final UserAvatarRenderer renderer = new UserAvatarRenderer() {
@Override
public void render(
@NonNull AvatarStyle style,
@NonNull User user,
@NonNull UserAvatarView target
) {
final Drawable placeholder = new ColorDrawable(Color.RED);
target.setAvatar(user.getImage(), placeholder);
target.setOnline(user.getOnline());
}
};
ChatUI.setUserAvatarRenderer(renderer);
}

@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
private void customizingChannelAvatarRenderer() {
ChannelAvatarRenderer renderer = new ChannelAvatarRenderer() {

@Override
public void render(
@NonNull AvatarStyle style,
@NonNull Channel channel,
@Nullable User currentUser,
ChannelAvatarViewProvider targetProvider
) {
Drawable placeholder = new ColorDrawable(Color.RED);

final AvatarImageView target1 = targetProvider.regular();
target1.setAvatar(channel.getImage(), placeholder);

final User singleUser = channel.getMembers().stream().findFirst().orElseThrow().getUser();
final UserAvatarView target2 = targetProvider.singleUser();
target2.setAvatar(singleUser.getImage(), placeholder);
target2.setOnline(singleUser.getOnline());

final String currentUserId = currentUser != null ? currentUser.getId() : null;
final List<User> users = channel.getMembers().stream()
.map(Member::getUser)
.filter(user -> !user.getId().equals(currentUserId))
.collect(Collectors.toList());
final List<AvatarImageView> target3 = targetProvider.userGroup(users.size());
for (int i = 0; i < target3.size(); i++) {
target3.get(i).setAvatar(users.get(i).getImage(), placeholder);
}
}
};
ChatUI.setChannelAvatarRenderer(renderer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ package io.getstream.chat.docs.kotlin.ui.general
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import io.getstream.chat.android.markdown.MarkdownTextTransformer
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.models.User
import io.getstream.chat.android.ui.ChatUI
import io.getstream.chat.android.ui.common.helper.DateFormatter
import io.getstream.chat.android.ui.common.helper.ImageHeadersProvider
Expand All @@ -27,6 +30,12 @@ import io.getstream.chat.android.ui.helper.transformer.ChatMessageTextTransforme
import io.getstream.chat.android.ui.navigation.ChatNavigationHandler
import io.getstream.chat.android.ui.navigation.ChatNavigator
import io.getstream.chat.android.ui.navigation.destinations.ChatDestination
import io.getstream.chat.android.ui.widgets.avatar.AvatarImageView
import io.getstream.chat.android.ui.widgets.avatar.AvatarStyle
import io.getstream.chat.android.ui.widgets.avatar.ChannelAvatarRenderer
import io.getstream.chat.android.ui.widgets.avatar.ChannelAvatarViewProvider
import io.getstream.chat.android.ui.widgets.avatar.UserAvatarRenderer
import io.getstream.chat.android.ui.widgets.avatar.UserAvatarView
import io.getstream.chat.docs.R
import java.text.DateFormat
import java.text.SimpleDateFormat
Expand Down Expand Up @@ -222,4 +231,41 @@ private class ChatUiSnippets {
private fun disablingVideoThumbnails() {
ChatUI.videoThumbnailsEnabled = false
}

private fun customizingUserAvatarRenderer() {
ChatUI.userAvatarRenderer = object : UserAvatarRenderer {
override fun render(style: AvatarStyle, user: User, target: UserAvatarView) {
val placeholder = ColorDrawable(Color.RED)
target.setAvatar(avatar = user.image, placeholder = placeholder)
target.setOnline(user.online)
}
}
}

private fun customizingChannelAvatarRenderer() {
ChatUI.channelAvatarRenderer = object : ChannelAvatarRenderer {
override fun render(
style: AvatarStyle,
channel: Channel,
currentUser: User?,
targetProvider: ChannelAvatarViewProvider,
) {
val placeholder = ColorDrawable(Color.RED)

val target1: AvatarImageView = targetProvider.regular()
target1.setAvatar(avatar = channel.image, placeholder = placeholder)

val user = channel.members.first { it.user.id != currentUser?.id }.user
val target2: UserAvatarView = targetProvider.singleUser()
target2.setAvatar(avatar = user.image, placeholder = placeholder)
target2.setOnline(user.online)

val users = channel.members.filter { it.user.id != currentUser?.id }.map { it.user }
val target3: List<AvatarImageView> = targetProvider.userGroup(users.size)
target3.forEachIndexed { index, targetItem ->
targetItem.setAvatar(avatar = users[index].image, placeholder = placeholder)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<item>
<shape android:shape="rectangle" >
<solid android:color="#FFCC00"/>
</shape>
</item>

<item android:drawable="@drawable/ic_member" android:gravity="center" />

</layer-list>
Loading
Loading