diff --git a/CHANGELOG.md b/CHANGELOG.md index 2132df61fe1..3cb0ddc36e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ ### ✅ Added - Added Typing Users list to `ChannelItem`. [#4867](https://github.com/GetStream/stream-chat-android/pull/4867) - Added typing indicator on `ChannelLitsView`. [#4867](https://github.com/GetStream/stream-chat-android/pull/4867) +- Added options visibility customization for each channel in a list. [#4869](https://github.com/GetStream/stream-chat-android/pull/4869) ### ⚠️ Changed - Create new `bind()` method on `BaseChannelListItemViewHolder` that takes as parameter `ChannelItem`. [#4867](https://github.com/GetStream/stream-chat-android/pull/4867) diff --git a/stream-chat-android-client/detekt-baseline.xml b/stream-chat-android-client/detekt-baseline.xml index 872d7746b08..db4529a25e4 100644 --- a/stream-chat-android-client/detekt-baseline.xml +++ b/stream-chat-android-client/detekt-baseline.xml @@ -5,39 +5,12 @@ ComplexMethod:Attachment.kt$Attachment$override fun toString(): String ComplexMethod:Message.kt$Message$override fun toString(): String ComplexMethod:User.kt$User$override fun getComparableField(fieldName: String): Comparable<*>? - FinalNewline:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:1 - FinalNewline:io.getstream.chat.android.client.user.CurrentUserFetcher.kt:1 - FinalNewline:io.getstream.chat.android.client.user.CurrentUserFetcherTests.kt:1 - FinalNewline:io.getstream.chat.android.client.user.CurrentUserUrlBuilderTests.kt:1 ForbiddenComment:MessageUtils.kt$// TODO: type should be a sealed/class or enum at the client level LongParameterList:BaseChatModule.kt$BaseChatModule$( private val appContext: Context, private val clientScope: ClientScope, private val userScope: UserScope, private val config: ChatClientConfig, private val notificationsHandler: NotificationHandler, private val notificationConfig: NotificationConfig, private val fileUploader: FileUploader? = null, private val tokenManager: TokenManager = TokenManagerImpl(), private val customOkHttpClient: OkHttpClient? = null, private val lifecycle: Lifecycle, private val httpClientConfig: (OkHttpClient.Builder) -> OkHttpClient.Builder = { it }, ) MagicNumber:Attachment.kt$Attachment$9 MaxLineLength:Message.kt$* MaxLineLength:Reaction.kt$* - NewLineAtEndOfFile:CurrentUserFetcher.kt$io.getstream.chat.android.client.user.CurrentUserFetcher.kt - NewLineAtEndOfFile:CurrentUserFetcherTests.kt$io.getstream.chat.android.client.user.CurrentUserFetcherTests.kt - NewLineAtEndOfFile:CurrentUserUrlBuilderTests.kt$io.getstream.chat.android.client.user.CurrentUserUrlBuilderTests.kt - NewLineAtEndOfFile:WhenFetchCurrentUser.kt$io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt - NoBlankLineBeforeRbrace:io.getstream.chat.android.client.user.CurrentUserFetcher.kt:97 - NoConsecutiveBlankLines:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:130 - NoUnusedImports:io.getstream.chat.android.client.DevicesApiCallsTests.kt:26 - NoUnusedImports:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:14 - NoUnusedImports:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:25 - NoUnusedImports:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:5 - NoUnusedImports:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:6 - NoUnusedImports:io.getstream.chat.android.client.chatclient.WhenFetchCurrentUser.kt:9 - NoUnusedImports:io.getstream.chat.android.client.user.CurrentUserFetcher.kt:44 ReturnCount:Channel.kt$@InternalStreamChatApi public fun Channel.addMember(member: Member?): Channel - ReturnCount:CurrentUserFetcher.kt$CurrentUserFetcherImpl$override suspend fun fetch(): Result<User> - TooGenericExceptionCaught:CurrentUserFetcher.kt$CurrentUserFetcherImpl$e: Throwable - TooGenericExceptionCaught:CurrentUserFetcher.kt$CurrentUserFetcherImpl$t: Throwable - UnusedImports:CurrentUserFetcher.kt$import java.net.URI - UnusedImports:DevicesApiCallsTests.kt$import kotlinx.coroutines.ExperimentalCoroutinesApi - UnusedImports:WhenFetchCurrentUser.kt$import io.getstream.chat.android.client.api.models.QueryChannelRequest - UnusedImports:WhenFetchCurrentUser.kt$import io.getstream.chat.android.client.call.CoroutineCall - UnusedImports:WhenFetchCurrentUser.kt$import io.getstream.chat.android.client.errors.ChatError - UnusedImports:WhenFetchCurrentUser.kt$import io.getstream.chat.android.client.plugin.listeners.QueryChannelListener - UnusedImports:WhenFetchCurrentUser.kt$import org.mockito.kotlin.doAnswer UnusedPrivateMember:BaseChatModule.kt$BaseChatModule$private val clientScope: ClientScope UnusedPrivateMember:ChatEventsObservable.kt$ChatEventsObservable$private val logger = StreamLog.getLogger("Chat:EventsObservable") 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 e791f8003b7..2f0f02c7bff 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 @@ -984,6 +984,8 @@ public final class io/getstream/chat/android/ui/channel/list/ChannelListView : a 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 public final fun setErrorEventHandler (Lio/getstream/chat/android/ui/channel/list/ChannelListView$ErrorEventHandler;)V + public final fun setIsDeleteOptionVisible (Lkotlin/jvm/functions/Function1;)V + public final fun setIsMoreOptionsVisible (Lkotlin/jvm/functions/Function1;)V public final fun setItemSeparator (I)V public final fun setItemSeparatorHeight (I)V public final fun setLoadingView (Landroid/view/View;)V @@ -1027,6 +1029,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$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; + public abstract fun invoke (Lio/getstream/chat/android/client/models/Channel;)Ljava/lang/Boolean; +} + +public final class io/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate$Companion { +} + public abstract interface class io/getstream/chat/android/ui/channel/list/ChannelListView$EndReachedListener { public abstract fun onEndReached ()V } @@ -1185,6 +1196,7 @@ public class io/getstream/chat/android/ui/channel/list/adapter/viewholder/Channe 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; + protected final fun getVisibilityContainer ()Lio/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainer; } public abstract interface class io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListListenerContainer { @@ -1196,6 +1208,11 @@ public abstract interface class io/getstream/chat/android/ui/channel/list/adapte public abstract fun getUserClickListener ()Lio/getstream/chat/android/ui/channel/list/ChannelListView$UserClickListener; } +public abstract interface class io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainer { + public abstract fun isDeleteOptionVisible ()Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate; + public abstract fun isMoreOptionsVisible ()Lio/getstream/chat/android/ui/channel/list/ChannelListView$ChannelOptionVisibilityPredicate; +} + public abstract class io/getstream/chat/android/ui/channel/list/adapter/viewholder/SwipeViewHolder : io/getstream/chat/android/ui/channel/list/adapter/viewholder/BaseChannelListItemViewHolder { public fun (Landroid/view/View;)V public abstract fun getClosedX ()F diff --git a/stream-chat-android-ui-components/detekt-baseline.xml b/stream-chat-android-ui-components/detekt-baseline.xml index 33f82b1f8f4..b2dc8a3f31c 100644 --- a/stream-chat-android-ui-components/detekt-baseline.xml +++ b/stream-chat-android-ui-components/detekt-baseline.xml @@ -86,11 +86,11 @@ MaxLineLength:AutoLinkableTextTransformer.kt$AutoLinkableTextTransformer$* MaxLineLength:AvatarView.kt$AvatarView$* MaxLineLength:ChannelActionsDialogViewStyle.kt$ChannelActionsDialogViewStyle$* - MaxLineLength:ChannelListPayloadDiff.kt$ChannelListPayloadDiff$return nameChanged || avatarViewChanged || usersChanged || lastMessageChanged || readStateChanged || unreadCountChanged || extraDataChanged 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$* MaxLineLength:ChannelListViewStyle.kt$ChannelListViewStyle$* + MaxLineLength:ChannelListVisibilityContainerImpl.kt$ChannelListVisibilityContainerImpl$override MaxLineLength:ChatUI.kt$ChatUI$public MaxLineLength:Context.kt$theme.resolveAttribute(R.attr.streamUiTheme, typedValue, true) -> ContextThemeWrapper(this, typedValue.resourceId) MaxLineLength:DefaultMessageComposerLeadingContent.kt$DefaultMessageComposerLeadingContent$binding.attachmentsButton.isVisible = style.attachmentsButtonVisible && canSendMessage && canUploadFile && !isInEditMode 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 cf82d79d2d5..fb6c467b768 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 @@ -296,6 +296,24 @@ public class ChannelListView : FrameLayout { simpleChannelListView.setMoreOptionsClickListener(listener) } + /** + * Allows clients to set a visibility controller for the "more options" icon in ViewHolder items. + * + * @param isMoreOptionsVisible The callback to be invoked when the visibility of "more options" gets checked. + */ + public fun setIsMoreOptionsVisible(isMoreOptionsVisible: (Channel) -> Boolean) { + simpleChannelListView.setIsMoreOptionsVisible(isMoreOptionsVisible) + } + + /** + * Allows clients to set a visibility controller for the "delete option" icon in ViewHolder items. + * + * @param isDeleteOptionVisible The callback to be invoked when the visibility of "delete option" gets checked. + */ + public fun setIsDeleteOptionVisible(isDeleteOptionVisible: (Channel) -> Boolean) { + simpleChannelListView.setIsDeleteOptionVisible(isDeleteOptionVisible) + } + /** * Allows a client to set a click listener to be notified of "channel info" clicks in the "more options" menu. * @@ -501,6 +519,23 @@ public class ChannelListView : FrameLayout { public fun onLongClick(channel: Channel): Boolean } + public fun interface ChannelOptionVisibilityPredicate : Function1 { + public companion object { + @JvmField + public val DEFAULT: ChannelOptionVisibilityPredicate = ChannelOptionVisibilityPredicate { + // option is visible by default + true + } + } + + /** + * Called to check option's visibility for the specified [channel]. + * + * @return True if the option is visible. + */ + override fun invoke(p1: Channel): Boolean + } + public fun interface EndReachedListener { public fun onEndReached() } 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 7fc01043dbc..a59eea01b19 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 @@ -29,6 +29,9 @@ public open class ChannelListItemViewHolderFactory { protected lateinit var listenerContainer: ChannelListListenerContainer private set + protected lateinit var visibilityContainer: ChannelListVisibilityContainer + private set + protected lateinit var style: ChannelListViewStyle private set @@ -36,6 +39,10 @@ public open class ChannelListItemViewHolderFactory { this.listenerContainer = listenerContainer } + internal fun setVisibilityContainer(visibilityContainer: ChannelListVisibilityContainer) { + this.visibilityContainer = visibilityContainer + } + internal fun setStyle(style: ChannelListViewStyle) { this.style = style } @@ -80,6 +87,8 @@ public open class ChannelListItemViewHolderFactory { listenerContainer.userClickListener, listenerContainer.swipeListener, style, + visibilityContainer.isMoreOptionsVisible, + visibilityContainer.isDeleteOptionVisible, ) } @@ -94,6 +103,9 @@ public open class ChannelListItemViewHolderFactory { if (!::listenerContainer.isInitialized) { listenerContainer = ChannelListListenerContainerImpl() } + if (!::visibilityContainer.isInitialized) { + visibilityContainer = ChannelListVisibilityContainerImpl() + } 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/ChannelListVisibilityContainer.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainer.kt new file mode 100644 index 00000000000..18fd597f3bf --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainer.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.ChannelOptionVisibilityPredicate + +public sealed interface ChannelListVisibilityContainer { + public val isMoreOptionsVisible: ChannelOptionVisibilityPredicate + public val isDeleteOptionVisible: ChannelOptionVisibilityPredicate +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainerImpl.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainerImpl.kt new file mode 100644 index 00000000000..b29ddfc5959 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/channel/list/adapter/viewholder/ChannelListVisibilityContainerImpl.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2023 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.ChannelOptionVisibilityPredicate + +internal class ChannelListVisibilityContainerImpl( + isMoreOptionsVisible: ChannelOptionVisibilityPredicate = ChannelOptionVisibilityPredicate.DEFAULT, + isDeleteOptionVisible: ChannelOptionVisibilityPredicate = ChannelOptionVisibilityPredicate.DEFAULT, +) : ChannelListVisibilityContainer { + + override var isMoreOptionsVisible: ChannelOptionVisibilityPredicate by ListenerDelegate(isMoreOptionsVisible) { realPredicate -> + ChannelOptionVisibilityPredicate { channel -> + realPredicate().invoke(channel) + } + } + + override var isDeleteOptionVisible: ChannelOptionVisibilityPredicate by ListenerDelegate(isDeleteOptionVisible) { realPredicate -> + ChannelOptionVisibilityPredicate { channel -> + realPredicate().invoke(channel) + } + } +} 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 5862b89aeb4..a22abe6ccc9 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 @@ -61,6 +61,8 @@ internal class ChannelViewHolder @JvmOverloads constructor( private val userClickListener: ChannelListView.UserClickListener, private val swipeListener: ChannelListView.SwipeListener, private val style: ChannelListViewStyle, + private val isMoreOptionsVisible: ChannelListView.ChannelOptionVisibilityPredicate, + private val isDeleteOptionsVisible: ChannelListView.ChannelOptionVisibilityPredicate, private val binding: StreamUiChannelListItemViewBinding = StreamUiChannelListItemViewBinding.inflate( parent.streamThemeInflater, parent, @@ -184,7 +186,7 @@ internal class ChannelViewHolder @JvmOverloads constructor( var optionsCount = 0 binding.itemBackgroundView.moreOptionsImageView.apply { - if (style.optionsEnabled) { + if (style.optionsEnabled && isMoreOptionsVisible(channel)) { isVisible = true optionsCount++ } else { @@ -193,7 +195,7 @@ internal class ChannelViewHolder @JvmOverloads constructor( } binding.itemBackgroundView.deleteImageView.apply { val canDeleteChannel = channel.ownCapabilities.contains(ChannelCapabilities.DELETE_CHANNEL) - if (canDeleteChannel && style.deleteEnabled) { + if (style.deleteEnabled && canDeleteChannel && isDeleteOptionsVisible(channel)) { isVisible = true optionsCount++ } else { 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 18b071097ba..95c8df55a9e 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 @@ -31,6 +31,7 @@ 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.ChannelListItemViewHolderFactory import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListListenerContainerImpl +import io.getstream.chat.android.ui.channel.list.adapter.viewholder.ChannelListVisibilityContainerImpl import io.getstream.chat.android.ui.channel.list.adapter.viewholder.internal.ChannelItemSwipeListener import io.getstream.chat.android.ui.common.extensions.internal.cast import io.getstream.chat.android.ui.common.extensions.internal.getDrawableCompat @@ -54,6 +55,8 @@ internal class SimpleChannelListView @JvmOverloads constructor( internal val listenerContainer = ChannelListListenerContainerImpl() + internal val visibilityContainer = ChannelListVisibilityContainerImpl() + private lateinit var style: ChannelListViewStyle init { @@ -96,6 +99,7 @@ internal class SimpleChannelListView @JvmOverloads constructor( } viewHolderFactory.setListenerContainer(this.listenerContainer) + viewHolderFactory.setVisibilityContainer(this.visibilityContainer) viewHolderFactory.setStyle(style) adapter = ChannelListItemAdapter(viewHolderFactory) @@ -134,6 +138,14 @@ internal class SimpleChannelListView @JvmOverloads constructor( listenerContainer.moreOptionsClickListener = listener ?: ChannelListView.ChannelClickListener.DEFAULT } + fun setIsMoreOptionsVisible(isMoreOptionsVisible: ChannelListView.ChannelOptionVisibilityPredicate) { + visibilityContainer.isMoreOptionsVisible = isMoreOptionsVisible + } + + fun setIsDeleteOptionVisible(isDeleteOptionVisible: ChannelListView.ChannelOptionVisibilityPredicate) { + visibilityContainer.isDeleteOptionVisible = isDeleteOptionVisible + } + fun setSwipeListener(listener: ChannelListView.SwipeListener?) { listenerContainer.swipeListener = listener ?: ChannelListView.SwipeListener.DEFAULT }