From 55dc9548353dcd8aded5f190313379c8b95da0a4 Mon Sep 17 00:00:00 2001 From: kanat <> Date: Wed, 16 Aug 2023 16:38:14 -0700 Subject: [PATCH 1/3] [i110] control options icon for each channel (cherry picked from commit 5d7902250d466de3dc93a9db25d45c00ea8439ab) # Conflicts: # stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer.kt # stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainerImpl.kt # stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/internal/SimpleChannelListView.kt --- .../sdk/chat/utils/ListenerDelegate.kt | 1 + .../ui/channel/list/ChannelListView.kt | 39 ++++++++++++++++++- .../ChannelListIconProviderContainer.kt | 24 ++++++++++++ .../ChannelListIconProviderContainerImpl.kt | 38 ++++++++++++++++++ .../ChannelListItemViewHolderFactory.kt | 12 ++++++ .../viewholder/internal/ChannelViewHolder.kt | 8 +++- .../list/internal/SimpleChannelListView.kt | 12 ++++++ 7 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer.kt create mode 100644 stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainerImpl.kt diff --git a/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/utils/ListenerDelegate.kt b/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/utils/ListenerDelegate.kt index d468ef080aa..d5a4a9122f8 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/utils/ListenerDelegate.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/utils/ListenerDelegate.kt @@ -37,6 +37,7 @@ import kotlin.reflect.KProperty * wrapped can be referenced by calling the realListener() method. This * function always returns the current listener, even if it changes. */ +// TODO Needs to be renamed to something like WrapDelegate. It is no longer used for listeners only. @InternalStreamChatApi public class ListenerDelegate( initialValue: L, diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/ChannelListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/ChannelListView.kt index fb6c467b768..2ddcbec852a 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/ChannelListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/ChannelListView.kt @@ -17,6 +17,7 @@ package io.getstream.chat.android.ui.channel.list import android.content.Context +import android.graphics.drawable.Drawable import android.os.Bundle import android.os.Parcelable import android.util.AttributeSet @@ -314,6 +315,24 @@ public class ChannelListView : FrameLayout { simpleChannelListView.setIsDeleteOptionVisible(isDeleteOptionVisible) } + /** + * Allows clients to override a "more options" icon in ViewHolder items. + * + * @param getMoreOptionsIcon Provides icon for a "more options". + */ + public fun setMoreOptionsIconProvider(getMoreOptionsIcon: (Channel) -> Drawable?) { + simpleChannelListView.setMoreOptionsIconProvider(getMoreOptionsIcon) + } + + /** + * Allows clients to override a "delete option" icon in ViewHolder items. + * + * @param getDeleteOptionIcon Provides icon for delete option. + */ + public fun setDeleteOptionIconProvider(getDeleteOptionIcon: (Channel) -> Drawable?) { + simpleChannelListView.setDeleteOptionIconProvider(getDeleteOptionIcon) + } + /** * Allows a client to set a click listener to be notified of "channel info" clicks in the "more options" menu. * @@ -533,7 +552,25 @@ public class ChannelListView : FrameLayout { * * @return True if the option is visible. */ - override fun invoke(p1: Channel): Boolean + override fun invoke(channel: Channel): Boolean + } + + public fun interface ChannelOptionIconProvider : Function1 { + + public companion object { + @JvmField + public val DEFAULT: ChannelOptionIconProvider = ChannelOptionIconProvider { + // option has no customized icon by default + null + } + } + + /** + * Called to provide option's icon for the specified [channel]. + * + * @return Drawable which overrides ChannelListViewStyle values. + */ + override fun invoke(channel: Channel): Drawable? } public fun interface EndReachedListener { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer.kt new file mode 100644 index 00000000000..d4ad2d24912 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.ui.channel.list.adapter.viewholder + +import io.getstream.chat.android.ui.channel.list.ChannelListView.ChannelOptionIconProvider + +public sealed interface ChannelListIconProviderContainer { + public val getMoreOptionsIcon: ChannelOptionIconProvider + public val getDeleteOptionIcon: ChannelOptionIconProvider +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainerImpl.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainerImpl.kt new file mode 100644 index 00000000000..6e832c86c16 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainerImpl.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.ui.channel.list.adapter.viewholder + +import com.getstream.sdk.chat.utils.ListenerDelegate +import io.getstream.chat.android.ui.channel.list.ChannelListView.ChannelOptionIconProvider + +internal class ChannelListIconProviderContainerImpl( + getMoreOptionsIcon: ChannelOptionIconProvider = ChannelOptionIconProvider.DEFAULT, + getDeleteOptionIcon: ChannelOptionIconProvider = ChannelOptionIconProvider.DEFAULT, +) : ChannelListIconProviderContainer { + + override var getMoreOptionsIcon: ChannelOptionIconProvider by ListenerDelegate(getMoreOptionsIcon) { realPredicate -> + ChannelOptionIconProvider { channel -> + realPredicate().invoke(channel) + } + } + + override var getDeleteOptionIcon: ChannelOptionIconProvider by ListenerDelegate(getDeleteOptionIcon) { realPredicate -> + ChannelOptionIconProvider { channel -> + realPredicate().invoke(channel) + } + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListItemViewHolderFactory.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListItemViewHolderFactory.kt index a59eea01b19..2f6e81a9546 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListItemViewHolderFactory.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListItemViewHolderFactory.kt @@ -32,6 +32,9 @@ public open class ChannelListItemViewHolderFactory { protected lateinit var visibilityContainer: ChannelListVisibilityContainer private set + protected lateinit var iconProviderContainer: ChannelListIconProviderContainer + private set + protected lateinit var style: ChannelListViewStyle private set @@ -43,6 +46,10 @@ public open class ChannelListItemViewHolderFactory { this.visibilityContainer = visibilityContainer } + internal fun setIconProviderContainer(iconProviderContainer: ChannelListIconProviderContainer) { + this.iconProviderContainer = iconProviderContainer + } + internal fun setStyle(style: ChannelListViewStyle) { this.style = style } @@ -89,6 +96,8 @@ public open class ChannelListItemViewHolderFactory { style, visibilityContainer.isMoreOptionsVisible, visibilityContainer.isDeleteOptionVisible, + iconProviderContainer.getMoreOptionsIcon, + iconProviderContainer.getDeleteOptionIcon, ) } @@ -106,6 +115,9 @@ public open class ChannelListItemViewHolderFactory { if (!::visibilityContainer.isInitialized) { visibilityContainer = ChannelListVisibilityContainerImpl() } + if (!::iconProviderContainer.isInitialized) { + iconProviderContainer = ChannelListIconProviderContainerImpl() + } if (!::style.isInitialized) { style = ChannelListViewStyle(context, null) } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/internal/ChannelViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/internal/ChannelViewHolder.kt index 70d11bab04f..4955dc671a2 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/internal/ChannelViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/internal/ChannelViewHolder.kt @@ -62,7 +62,9 @@ internal class ChannelViewHolder @JvmOverloads constructor( private val swipeListener: ChannelListView.SwipeListener, private val style: ChannelListViewStyle, private val isMoreOptionsVisible: ChannelListView.ChannelOptionVisibilityPredicate, - private val isDeleteOptionsVisible: ChannelListView.ChannelOptionVisibilityPredicate, + private val isDeleteOptionVisible: ChannelListView.ChannelOptionVisibilityPredicate, + private val getMoreOptionsIcon: ChannelListView.ChannelOptionIconProvider, + private val getDeleteOptionIcon: ChannelListView.ChannelOptionIconProvider, private val binding: StreamUiChannelListItemViewBinding = StreamUiChannelListItemViewBinding.inflate( parent.streamThemeInflater, parent, @@ -188,6 +190,7 @@ internal class ChannelViewHolder @JvmOverloads constructor( binding.itemBackgroundView.moreOptionsImageView.apply { if (style.optionsEnabled && isMoreOptionsVisible(channel)) { isVisible = true + getMoreOptionsIcon.invoke(channel)?.also { setImageDrawable(it) } optionsCount++ } else { isVisible = false @@ -195,8 +198,9 @@ internal class ChannelViewHolder @JvmOverloads constructor( } binding.itemBackgroundView.deleteImageView.apply { val canDeleteChannel = channel.ownCapabilities.contains(ChannelCapabilities.DELETE_CHANNEL) - if (style.deleteEnabled && canDeleteChannel && isDeleteOptionsVisible(channel)) { + if (style.deleteEnabled && canDeleteChannel && isDeleteOptionVisible(channel)) { isVisible = true + getDeleteOptionIcon.invoke(channel)?.also { setImageDrawable(it) } optionsCount++ } else { isVisible = false diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/internal/SimpleChannelListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/internal/SimpleChannelListView.kt index 95c8df55a9e..b4400407124 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/internal/SimpleChannelListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/internal/SimpleChannelListView.kt @@ -29,6 +29,7 @@ import io.getstream.chat.android.ui.channel.list.ChannelListView import io.getstream.chat.android.ui.channel.list.ChannelListViewStyle import io.getstream.chat.android.ui.channel.list.adapter.ChannelListItem import io.getstream.chat.android.ui.channel.list.adapter.internal.ChannelListItemAdapter +import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListIconProviderContainerImpl import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListItemViewHolderFactory import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListListenerContainerImpl import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListVisibilityContainerImpl @@ -57,6 +58,8 @@ internal class SimpleChannelListView @JvmOverloads constructor( internal val visibilityContainer = ChannelListVisibilityContainerImpl() + internal val iconProviderContainer = ChannelListIconProviderContainerImpl() + private lateinit var style: ChannelListViewStyle init { @@ -100,6 +103,7 @@ internal class SimpleChannelListView @JvmOverloads constructor( viewHolderFactory.setListenerContainer(this.listenerContainer) viewHolderFactory.setVisibilityContainer(this.visibilityContainer) + viewHolderFactory.setIconProviderContainer(this.iconProviderContainer) viewHolderFactory.setStyle(style) adapter = ChannelListItemAdapter(viewHolderFactory) @@ -146,6 +150,14 @@ internal class SimpleChannelListView @JvmOverloads constructor( visibilityContainer.isDeleteOptionVisible = isDeleteOptionVisible } + fun setMoreOptionsIconProvider(getMoreOptionsIcon: ChannelListView.ChannelOptionIconProvider) { + iconProviderContainer.getMoreOptionsIcon = getMoreOptionsIcon + } + + fun setDeleteOptionIconProvider(getDeleteOptionIcon: ChannelListView.ChannelOptionIconProvider) { + iconProviderContainer.getDeleteOptionIcon = getDeleteOptionIcon + } + fun setSwipeListener(listener: ChannelListView.SwipeListener?) { listenerContainer.swipeListener = listener ?: ChannelListView.SwipeListener.DEFAULT } From 551540d1178653038999a587d925132654997519 Mon Sep 17 00:00:00 2001 From: kanat <> Date: Fri, 18 Aug 2023 08:44:41 -0700 Subject: [PATCH 2/3] [i110] fix api-dump & detekt --- .../api/stream-chat-android-ui-components.api | 17 +++++++++++++++++ .../detekt-baseline.xml | 1 + 2 files changed, 18 insertions(+) diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index c2df24c5d1f..8a7d2129475 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -988,6 +988,7 @@ public final class io/getstream/chat/android/ui/channel/list/ChannelListView : a public final fun setChannelListUpdateListener (Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelListUpdateListener;)V public final fun setChannelLongClickListener (Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelLongClickListener;)V public final fun setChannels (Ljava/util/List;)V + public final fun setDeleteOptionIconProvider (Lkotlin/jvm/functions/Function1;)V public final fun setEmptyStateView (Landroid/view/View;)V public final fun setEmptyStateView (Landroid/view/View;Landroid/widget/FrameLayout$LayoutParams;)V public static synthetic fun setEmptyStateView$default (Lio/getstream/chat/android/ui/channel/list/ChannelListView;Landroid/view/View;Landroid/widget/FrameLayout$LayoutParams;ILjava/lang/Object;)V @@ -1000,6 +1001,7 @@ public final class io/getstream/chat/android/ui/channel/list/ChannelListView : a public final fun setLoadingView (Landroid/view/View;Landroid/widget/FrameLayout$LayoutParams;)V public static synthetic fun setLoadingView$default (Lio/getstream/chat/android/ui/channel/list/ChannelListView;Landroid/view/View;Landroid/widget/FrameLayout$LayoutParams;ILjava/lang/Object;)V public final fun setMoreOptionsClickListener (Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelClickListener;)V + public final fun setMoreOptionsIconProvider (Lkotlin/jvm/functions/Function1;)V public final fun setOnEndReachedListener (Lio/getstream/chat/android/ui/channel/list/ChannelListView$EndReachedListener;)V public final fun setPaginationEnabled (Z)V public final fun setShouldDrawItemSeparatorOnLastItem (Z)V @@ -1037,6 +1039,15 @@ public abstract interface class io/getstream/chat/android/ui/channel/list/Channe public final class io/getstream/chat/android/ui/channel/list/ChannelListView$ChannelLongClickListener$Companion { } +public abstract interface class io/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider : kotlin/jvm/functions/Function1 { + public static final field Companion Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider$Companion; + public static final field DEFAULT Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider; + public abstract fun invoke (Lio/getstream/chat/android/client/models/Channel;)Landroid/graphics/drawable/Drawable; +} + +public final class io/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider$Companion { +} + public abstract interface class io/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate : kotlin/jvm/functions/Function1 { public static final field Companion Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate$Companion; public static final field DEFAULT Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate; @@ -1197,11 +1208,17 @@ public abstract class io/getstream/chat/android/ui/channel/list/adapter/viewhold public fun bind (Lio/getstream/chat/android/ui/channel/list/adapter/ChannelListItem$ChannelItem;Lio/getstream/chat/android/ui/channel/list/adapter/ChannelListPayloadDiff;)V } +public abstract interface class io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer { + public abstract fun getGetDeleteOptionIcon ()Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider; + public abstract fun getGetMoreOptionsIcon ()Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionIconProvider; +} + public class io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListItemViewHolderFactory { public fun ()V protected fun createChannelViewHolder (Landroid/view/ViewGroup;)Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/BaseChannelListItemViewHolder; protected fun createLoadingMoreViewHolder (Landroid/view/ViewGroup;)Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/BaseChannelListItemViewHolder; public fun createViewHolder (Landroid/view/ViewGroup;I)Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/BaseChannelListItemViewHolder; + protected final fun getIconProviderContainer ()Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListIconProviderContainer; public fun getItemViewType (Lio/getstream/chat/android/ui/channel/list/adapter/ChannelListItem;)I protected final fun getListenerContainer ()Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListListenerContainer; protected final fun getStyle ()Lio/getstream/chat/android/ui/channel/list/ChannelListViewStyle; diff --git a/stream-chat-android-ui-components/detekt-baseline.xml b/stream-chat-android-ui-components/detekt-baseline.xml index b2dc8a3f31c..8ce6b2a3a4b 100644 --- a/stream-chat-android-ui-components/detekt-baseline.xml +++ b/stream-chat-android-ui-components/detekt-baseline.xml @@ -86,6 +86,7 @@ MaxLineLength:AutoLinkableTextTransformer.kt$AutoLinkableTextTransformer$* MaxLineLength:AvatarView.kt$AvatarView$* MaxLineLength:ChannelActionsDialogViewStyle.kt$ChannelActionsDialogViewStyle$* + MaxLineLength:ChannelListIconProviderContainerImpl.kt$ChannelListIconProviderContainerImpl$override MaxLineLength:ChannelListView.kt$ChannelListView$is ChannelListViewModel.ErrorEvent.DeleteChannelError -> R.string.stream_ui_channel_list_error_delete_channel MaxLineLength:ChannelListViewModel.kt$ChannelListViewModel$* MaxLineLength:ChannelListViewModelFactory.kt$ChannelListViewModelFactory$* From cd4d3ceabf4247c35167b2480cde65e5ccb08dd2 Mon Sep 17 00:00:00 2001 From: kanat <> Date: Fri, 18 Aug 2023 08:45:54 -0700 Subject: [PATCH 3/3] [i110] add CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c361deecdb0..43250297d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ ### ✅ Added - Added `MessageListView.setCustomItemAnimator` to allow customizing the item animator used by the `MessageListView`. [#4926](https://github.com/GetStream/stream-chat-android/pull/4926) +- Added `ChannelListView.setMoreOptionsIconProvider` and `ChannelListView.setDeleteOptionIconProvider` to allow customizing the options icons used by the `ChannelListView`. [#4928](https://github.com/GetStream/stream-chat-android/pull/4928) ### ⚠️ Changed