diff --git a/CHANGELOG.md b/CHANGELOG.md index 24aa80c3191..3becd356ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ ### ⚠️ Changed - Made `MessageReplyView` publicly available. [#5057](https://github.com/GetStream/stream-chat-android/pull/5057) +- Made `MessageComposerContent` descendants extensible/reusable. [#5061](https://github.com/GetStream/stream-chat-android/pull/5061) ### ❌ Removed diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt index 23463a15fd1..7734b65181c 100644 --- a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt @@ -53,6 +53,7 @@ import io.getstream.chat.android.ui.message.list.viewmodel.bindView import io.getstream.chat.android.ui.message.list.viewmodel.factory.MessageListViewModelFactory import io.getstream.chat.ui.sample.common.navigateSafely import io.getstream.chat.ui.sample.databinding.FragmentChatBinding +import io.getstream.chat.ui.sample.feature.chat.composer.CustomMessageComposerLeadingContent import io.getstream.chat.ui.sample.feature.common.ConfirmationDialogFragment import io.getstream.chat.ui.sample.util.extensions.useAdjustResize import java.util.Calendar @@ -193,6 +194,7 @@ class ChatFragment : Fragment() { } } } + binding.messageListView.setMessageReplyHandler { _, message -> messageComposerViewModel.performMessageAction(Reply(message)) } @@ -213,6 +215,12 @@ class ChatFragment : Fragment() { } } } + + if (OVERRIDE_LEADING_CONTENT) { + binding.messageComposerView.setLeadingContent( + CustomMessageComposerLeadingContent(requireContext()) + ) + } } private fun initMessagesViewModel() { @@ -303,4 +311,8 @@ class ChatFragment : Fragment() { val chatError = error() as ChatNetworkError return chatError.streamCode == 4 } + + private companion object { + private const val OVERRIDE_LEADING_CONTENT = false + } } diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/composer/CustomMessageComposerLeadingContent.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/composer/CustomMessageComposerLeadingContent.kt new file mode 100644 index 00000000000..73da91e7583 --- /dev/null +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/composer/CustomMessageComposerLeadingContent.kt @@ -0,0 +1,35 @@ +/* + * 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.ui.sample.feature.chat.composer + +import android.content.Context +import android.util.AttributeSet +import io.getstream.chat.android.common.composer.MessageComposerState +import io.getstream.chat.android.core.ExperimentalStreamChatApi +import io.getstream.chat.android.ui.message.composer.content.DefaultMessageComposerLeadingContent + +@OptIn(ExperimentalStreamChatApi::class) +class CustomMessageComposerLeadingContent : DefaultMessageComposerLeadingContent { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + override fun renderState(state: MessageComposerState) { + super.renderState(state) + binding.attachmentsButton.isEnabled = state.attachments.isEmpty() + } +} 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 8ff2314c903..faf9d496eeb 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 @@ -2232,78 +2232,147 @@ public final class io/getstream/chat/android/ui/message/composer/attachment/fact public fun onCreateViewHolder (Landroid/view/ViewGroup;Lkotlin/jvm/functions/Function1;)Lio/getstream/chat/android/ui/message/composer/attachment/AttachmentPreviewViewHolder; } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public abstract interface class io/getstream/chat/android/ui/message/composer/content/AttachmentAdapter { + public abstract fun getItemCount ()I + public abstract fun setAttachments (Ljava/util/List;)V +} + +public abstract interface class io/getstream/chat/android/ui/message/composer/content/CommandSuggestionsAdapter { + public abstract fun getItemCount ()I + public abstract fun setItems (Ljava/util/List;)V +} + +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field attachmentsAdapter Lio/getstream/chat/android/ui/message/composer/content/AttachmentAdapter; + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultCenterContentBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V + protected fun buildAdapter (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)Landroidx/recyclerview/widget/RecyclerView$Adapter; public final fun getAttachmentRemovalListener ()Lkotlin/jvm/functions/Function1; + protected final fun getAttachmentsAdapter ()Lio/getstream/chat/android/ui/message/composer/content/AttachmentAdapter; + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultCenterContentBinding; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public final fun getTextInputChangeListener ()Lkotlin/jvm/functions/Function1; + protected fun renderAttachmentState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected fun renderReplyState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected fun renderTextInputState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V public final fun setAttachmentRemovalListener (Lkotlin/jvm/functions/Function1;)V + protected final fun setAttachmentsAdapter (Lio/getstream/chat/android/ui/message/composer/content/AttachmentAdapter;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultCenterContentBinding;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V public final fun setTextInputChangeListener (Lkotlin/jvm/functions/Function1;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V + protected fun buildAdapter (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)Landroidx/recyclerview/widget/RecyclerView$Adapter; + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding; public final fun getCommandSelectionListener ()Lkotlin/jvm/functions/Function1; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding;)V public final fun setCommandSelectionListener (Lkotlin/jvm/functions/Function1;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultFooterContentBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V public final fun getAlsoSendToChannelSelectionListener ()Lkotlin/jvm/functions/Function1; + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultFooterContentBinding; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V public final fun setAlsoSendToChannelSelectionListener (Lkotlin/jvm/functions/Function1;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultFooterContentBinding;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultHeaderContentBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultHeaderContentBinding; public final fun getDismissActionClickListener ()Lkotlin/jvm/functions/Function0; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultHeaderContentBinding;)V public final fun setDismissActionClickListener (Lkotlin/jvm/functions/Function0;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultLeadingContentBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V public final fun getAttachmentsButtonClickListener ()Lkotlin/jvm/functions/Function0; + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultLeadingContentBinding; public final fun getCommandsButtonClickListener ()Lkotlin/jvm/functions/Function0; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V public final fun setAttachmentsButtonClickListener (Lkotlin/jvm/functions/Function0;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultLeadingContentBinding;)V public final fun setCommandsButtonClickListener (Lkotlin/jvm/functions/Function0;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field adapter Lio/getstream/chat/android/ui/message/composer/content/MentionSuggestionsAdapter; + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V + protected fun buildAdapter (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)Landroidx/recyclerview/widget/RecyclerView$Adapter; + protected final fun getAdapter ()Lio/getstream/chat/android/ui/message/composer/content/MentionSuggestionsAdapter; + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding; public final fun getMentionSelectionListener ()Lkotlin/jvm/functions/Function1; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected final fun setAdapter (Lio/getstream/chat/android/ui/message/composer/content/MentionSuggestionsAdapter;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiSuggestionListViewBinding;)V public final fun setMentionSelectionListener (Lkotlin/jvm/functions/Function1;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V } -public final class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { +public class io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent : android/widget/FrameLayout, io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { + protected field binding Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultTrailingContentBinding; + protected field style Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V public fun attachContext (Lio/getstream/chat/android/ui/message/composer/MessageComposerContext;)V + protected final fun getBinding ()Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultTrailingContentBinding; public final fun getSendMessageButtonClickListener ()Lkotlin/jvm/functions/Function0; + protected final fun getStyle ()Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle; public fun renderState (Lio/getstream/chat/android/common/composer/MessageComposerState;)V + protected final fun setBinding (Lio/getstream/chat/android/ui/databinding/StreamUiMessageComposerDefaultTrailingContentBinding;)V public final fun setSendMessageButtonClickListener (Lkotlin/jvm/functions/Function0;)V + protected final fun setStyle (Lio/getstream/chat/android/ui/message/composer/MessageComposerViewStyle;)V +} + +public abstract interface class io/getstream/chat/android/ui/message/composer/content/MentionSuggestionsAdapter { + public abstract fun getItemCount ()I + public abstract fun setItems (Ljava/util/List;)V } public abstract interface class io/getstream/chat/android/ui/message/composer/content/MessageComposerContent { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/MessageComposerView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/MessageComposerView.kt index 7f189d19a0b..e3f43c5285b 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/MessageComposerView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/MessageComposerView.kt @@ -294,6 +294,14 @@ public class MessageComposerView : ConstraintLayout { ) where V : View, V : MessageComposerContent { binding.leadingContent.removeAllViews() binding.leadingContent.addView(contentView.attachContext(), layoutParams) + if (contentView is DefaultMessageComposerLeadingContent) { + if (contentView.attachmentsButtonClickListener == null) { + contentView.attachmentsButtonClickListener = { attachmentsButtonClickListener() } + } + if (contentView.commandsButtonClickListener == null) { + contentView.commandsButtonClickListener = { commandsButtonClickListener() } + } + } } /** @@ -315,6 +323,14 @@ public class MessageComposerView : ConstraintLayout { ) where V : View, V : MessageComposerContent { binding.centerContent.removeAllViews() binding.centerContent.addView(contentView.attachContext(), layoutParams) + if (contentView is DefaultMessageComposerCenterContent) { + if (contentView.textInputChangeListener == null) { + contentView.textInputChangeListener = { textInputChangeListener(it) } + } + if (contentView.attachmentRemovalListener == null) { + contentView.attachmentRemovalListener = { attachmentRemovalListener(it) } + } + } } /** @@ -337,6 +353,11 @@ public class MessageComposerView : ConstraintLayout { ) where V : View, V : MessageComposerContent { binding.trailingContent.removeAllViews() binding.trailingContent.addView(contentView.attachContext(), layoutParams) + if (contentView is DefaultMessageComposerTrailingContent) { + if (contentView.sendMessageButtonClickListener == null) { + contentView.sendMessageButtonClickListener = { sendMessageButtonClickListener() } + } + } } /** @@ -358,6 +379,11 @@ public class MessageComposerView : ConstraintLayout { ) where V : View, V : MessageComposerContent { binding.footerContent.removeAllViews() binding.footerContent.addView(contentView.attachContext(), layoutParams) + if (contentView is DefaultMessageComposerFooterContent) { + if (contentView.alsoSendToChannelSelectionListener == null) { + contentView.alsoSendToChannelSelectionListener = { alsoSendToChannelSelectionListener(it) } + } + } } /** @@ -379,6 +405,11 @@ public class MessageComposerView : ConstraintLayout { ) where V : View, V : MessageComposerContent { binding.headerContent.removeAllViews() binding.headerContent.addView(contentView.attachContext(), layoutParams) + if (contentView is DefaultMessageComposerHeaderContent) { + if (contentView.dismissActionClickListener == null) { + contentView.dismissActionClickListener = { dismissActionClickListener() } + } + } } /** @@ -392,6 +423,11 @@ public class MessageComposerView : ConstraintLayout { */ public fun setMentionSuggestionsContent(contentView: V) where V : View, V : MessageComposerContent { mentionSuggestionsContentOverride = contentView.attachContext() + if (contentView is DefaultMessageComposerMentionSuggestionsContent) { + if (contentView.mentionSelectionListener == null) { + contentView.mentionSelectionListener = { mentionSelectionListener(it) } + } + } } /** @@ -405,6 +441,11 @@ public class MessageComposerView : ConstraintLayout { */ public fun setCommandSuggestionsContent(contentView: V) where V : View, V : MessageComposerContent { commandSuggestionsContentOverride = contentView.attachContext() + if (contentView is DefaultMessageComposerCommandSuggestionsContent) { + if (contentView.commandSelectionListener == null) { + contentView.commandSelectionListener = { commandSelectionListener(it) } + } + } } /** diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent.kt index 18771911fc3..aa65dc5f633 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCenterContent.kt @@ -46,35 +46,31 @@ import io.getstream.chat.android.ui.message.composer.attachment.AttachmentPrevie * Represents the default content shown at the center of [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiMessageComposerDefaultCenterContentBinding + protected lateinit var binding: StreamUiMessageComposerDefaultCenterContentBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** - * Text change listener invoked after each input change. + * Adapter used to render attachments previews list. */ - public var textInputChangeListener: (String) -> Unit = {} + protected lateinit var attachmentsAdapter: AttachmentAdapter /** - * Click listener for the remove attachment button. + * Text change listener invoked after each input change. */ - public var attachmentRemovalListener: (Attachment) -> Unit = {} + public var textInputChangeListener: ((String) -> Unit)? = null /** - * Adapter used to render attachments previews list. + * Click listener for the remove attachment button. */ - private val attachmentsAdapter: AttachmentPreviewAdapter by lazy { - AttachmentPreviewAdapter(ChatUI.attachmentPreviewFactoryManager) { attachment -> - attachmentRemovalListener(attachment) - } - } + public var attachmentRemovalListener: ((Attachment) -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -93,11 +89,18 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC */ private fun init() { binding = StreamUiMessageComposerDefaultCenterContentBinding.inflate(streamThemeInflater, this) - binding.messageEditText.doAfterTextChanged { editable: Editable? -> - textInputChangeListener(editable?.toString() ?: "") + textInputChangeListener?.invoke(editable?.toString() ?: "") } - binding.attachmentsRecyclerView.adapter = attachmentsAdapter + } + + @Suppress("UNCHECKED_CAST") + protected open fun buildAdapter( + style: MessageComposerViewStyle + ): T where T : RecyclerView.Adapter, T : AttachmentAdapter, VH : RecyclerView.ViewHolder { + return AttachmentPreviewAdapter(ChatUI.attachmentPreviewFactoryManager) { attachment -> + attachmentRemovalListener?.invoke(attachment) + } as T } /** @@ -107,7 +110,9 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC */ override fun attachContext(messageComposerContext: MessageComposerContext) { this.style = messageComposerContext.style - + val rvAdapter: RecyclerView.Adapter = buildAdapter(messageComposerContext.style) + attachmentsAdapter = rvAdapter as AttachmentAdapter + binding.attachmentsRecyclerView.adapter = rvAdapter binding.messageInputContainer.background = style.messageInputBackgroundDrawable binding.messageEditText.setTextStyle(style.messageInputTextStyle) binding.messageEditText.isVerticalScrollBarEnabled = style.messageInputScrollbarEnabled @@ -136,7 +141,7 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC * * @param state The state that will be used to render the updated UI. */ - private fun renderTextInputState(state: MessageComposerState) { + protected open fun renderTextInputState(state: MessageComposerState) { binding.messageEditText.apply { val currentValue = text.toString() val newValue = state.inputValue @@ -165,7 +170,7 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC * * @param state The state that will be used to render the updated UI. */ - private fun renderReplyState(state: MessageComposerState) { + protected open fun renderReplyState(state: MessageComposerState) { if (!style.messageInputShowReplyView) return val action = state.action @@ -187,12 +192,28 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC * * @param state The state that will be used to render the updated UI. */ - private fun renderAttachmentState(state: MessageComposerState) { + protected open fun renderAttachmentState(state: MessageComposerState) { binding.attachmentsRecyclerView.isVisible = state.attachments.isNotEmpty() attachmentsAdapter.setAttachments(state.attachments) } } +/** + * Adapter used to render attachments. + */ +public interface AttachmentAdapter { + + /** + * Sets the list of attachments to be displayed. + */ + public fun setAttachments(attachments: List) + + /** + * Returns the number of attachment items. + */ + public fun getItemCount(): Int +} + /** * [RecyclerView.Adapter] responsible for displaying attachment previews in a RecyclerView. * @@ -202,7 +223,7 @@ public class DefaultMessageComposerCenterContent : FrameLayout, MessageComposerC private class AttachmentPreviewAdapter( private val factoryManager: AttachmentPreviewFactoryManager, private val attachmentRemovalListener: (Attachment) -> Unit, -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter(), AttachmentAdapter { /** * The attachments that will be displayed in the list. @@ -215,7 +236,7 @@ private class AttachmentPreviewAdapter( * @param attachments */ @SuppressLint("NotifyDataSetChanged") - fun setAttachments(attachments: List) { + override fun setAttachments(attachments: List) { this.attachments.clear() this.attachments.addAll(attachments) notifyDataSetChanged() diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent.kt index 5218c2a545c..04f9d1e590a 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerCommandSuggestionsContent.kt @@ -41,28 +41,26 @@ import io.getstream.chat.android.ui.utils.extensions.applyTint * Represents the default command suggestion list popup shown above [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiSuggestionListViewBinding + protected lateinit var binding: StreamUiSuggestionListViewBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Adapter used to render command suggestions. */ - private val adapter: CommandsAdapter by lazy { - CommandsAdapter(style) { commandSelectionListener(it) } - } + private lateinit var adapter: CommandSuggestionsAdapter /** * Selection listener invoked when a command is selected. */ - public var commandSelectionListener: (Command) -> Unit = {} + public var commandSelectionListener: ((Command) -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -85,6 +83,13 @@ public class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, Mess binding.commandsTitleTextView.isVisible = true } + @Suppress("UNCHECKED_CAST") + protected open fun buildAdapter( + style: MessageComposerViewStyle + ): T where T : RecyclerView.Adapter, T : CommandSuggestionsAdapter, VH : RecyclerView.ViewHolder { + return CommandsAdapter(style) { commandSelectionListener?.invoke(it) } as T + } + /** * Initializes the content view with [MessageComposerContext]. * @@ -92,8 +97,9 @@ public class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, Mess */ override fun attachContext(messageComposerContext: MessageComposerContext) { this.style = messageComposerContext.style - - binding.suggestionsRecyclerView.adapter = adapter + val rvAdapter: RecyclerView.Adapter = buildAdapter(messageComposerContext.style) + adapter = rvAdapter as CommandSuggestionsAdapter + binding.suggestionsRecyclerView.adapter = rvAdapter binding.suggestionsCardView.setCardBackgroundColor(style.commandSuggestionsBackgroundColor) binding.commandsTitleTextView.text = style.commandSuggestionsTitleText binding.commandsTitleTextView.setTextStyle(style.commandSuggestionsTitleTextStyle) @@ -112,6 +118,22 @@ public class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, Mess } } +/** + * Adapter used to render command suggestions. + */ +public interface CommandSuggestionsAdapter { + + /** + * Sets the list of command suggestions to be displayed. + */ + public fun setItems(items: List) + + /** + * Returns the number of items in the adapter. + */ + public fun getItemCount(): Int +} + /** * [RecyclerView.Adapter] responsible for displaying command suggestions in a RecyclerView. * @@ -121,7 +143,7 @@ public class DefaultMessageComposerCommandSuggestionsContent : FrameLayout, Mess private class CommandsAdapter( private val style: MessageComposerViewStyle, private val commandSelectionListener: (Command) -> Unit, -) : SimpleListAdapter() { +) : SimpleListAdapter(), CommandSuggestionsAdapter { /** * Creates and instantiates a new instance of [CommandViewHolder]. diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent.kt index 089c69a8c09..faa1f0abcef 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerFooterContent.kt @@ -35,21 +35,21 @@ import io.getstream.chat.android.ui.message.composer.MessageComposerViewStyle * Represents the default content shown at the bottom of [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerFooterContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerFooterContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiMessageComposerDefaultFooterContentBinding + protected lateinit var binding: StreamUiMessageComposerDefaultFooterContentBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Selection listener for the "also send to channel" checkbox. */ - public var alsoSendToChannelSelectionListener: (Boolean) -> Unit = {} + public var alsoSendToChannelSelectionListener: ((Boolean) -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -69,7 +69,7 @@ public class DefaultMessageComposerFooterContent : FrameLayout, MessageComposerC private fun init() { binding = StreamUiMessageComposerDefaultFooterContentBinding.inflate(streamThemeInflater, this) binding.alsoSendToChannelCheckBox.setOnCheckedChangeListener { _, _ -> - alsoSendToChannelSelectionListener(binding.alsoSendToChannelCheckBox.isChecked) + alsoSendToChannelSelectionListener?.invoke(binding.alsoSendToChannelCheckBox.isChecked) } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent.kt index 6f700b2e88d..a09afcba500 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerHeaderContent.kt @@ -35,21 +35,21 @@ import io.getstream.chat.android.ui.message.composer.MessageComposerViewStyle * Represents the default content shown at the top of [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerHeaderContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerHeaderContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiMessageComposerDefaultHeaderContentBinding + protected lateinit var binding: StreamUiMessageComposerDefaultHeaderContentBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Click listener for the dismiss action button. */ - public var dismissActionClickListener: () -> Unit = {} + public var dismissActionClickListener: (() -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -68,7 +68,7 @@ public class DefaultMessageComposerHeaderContent : FrameLayout, MessageComposerC */ private fun init() { binding = StreamUiMessageComposerDefaultHeaderContentBinding.inflate(streamThemeInflater, this) - binding.dismissInputModeButton.setOnClickListener { dismissActionClickListener() } + binding.dismissInputModeButton.setOnClickListener { dismissActionClickListener?.invoke() } } /** diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent.kt index 88b676dbed2..071ea820e24 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerLeadingContent.kt @@ -39,26 +39,26 @@ import io.getstream.chat.android.ui.utils.extensions.setBorderlessRipple * Represents the default content shown at the start of [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerLeadingContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerLeadingContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiMessageComposerDefaultLeadingContentBinding + protected lateinit var binding: StreamUiMessageComposerDefaultLeadingContentBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Click listener for the pick attachments button. */ - public var attachmentsButtonClickListener: () -> Unit = {} + public var attachmentsButtonClickListener: (() -> Unit)? = null /** * Click listener for the pick commands button. */ - public var commandsButtonClickListener: () -> Unit = {} + public var commandsButtonClickListener: (() -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -77,8 +77,8 @@ public class DefaultMessageComposerLeadingContent : FrameLayout, MessageComposer */ private fun init() { binding = StreamUiMessageComposerDefaultLeadingContentBinding.inflate(streamThemeInflater, this) - binding.attachmentsButton.setOnClickListener { attachmentsButtonClickListener() } - binding.commandsButton.setOnClickListener { commandsButtonClickListener() } + binding.attachmentsButton.setOnClickListener { attachmentsButtonClickListener?.invoke() } + binding.commandsButton.setOnClickListener { commandsButtonClickListener?.invoke() } } /** diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent.kt index 0c15b36bf1b..481da125d0f 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerMentionSuggestionsContent.kt @@ -41,28 +41,26 @@ import io.getstream.chat.android.ui.utils.extensions.applyTint * Represents the default mention suggestion list popup shown above [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiSuggestionListViewBinding + protected lateinit var binding: StreamUiSuggestionListViewBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Adapter used to render mention suggestions. */ - private val adapter: MentionsAdapter by lazy { - MentionsAdapter(style) { mentionSelectionListener(it) } - } + protected lateinit var adapter: MentionSuggestionsAdapter /** * Selection listener invoked when a mention is selected. */ - public var mentionSelectionListener: (User) -> Unit = {} + public var mentionSelectionListener: ((User) -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -84,6 +82,13 @@ public class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, Mess binding.suggestionsCardView.isVisible = true } + @Suppress("UNCHECKED_CAST") + protected open fun buildAdapter( + style: MessageComposerViewStyle + ): T where T : RecyclerView.Adapter, T : MentionSuggestionsAdapter, VH : RecyclerView.ViewHolder { + return MentionsAdapter(style) { mentionSelectionListener?.invoke(it) } as T + } + /** * Initializes the content view with [MessageComposerContext]. * @@ -91,8 +96,9 @@ public class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, Mess */ override fun attachContext(messageComposerContext: MessageComposerContext) { this.style = messageComposerContext.style - - binding.suggestionsRecyclerView.adapter = adapter + val rvAdapter: RecyclerView.Adapter = buildAdapter(messageComposerContext.style) + adapter = rvAdapter as MentionSuggestionsAdapter + binding.suggestionsRecyclerView.adapter = rvAdapter binding.suggestionsCardView.setCardBackgroundColor(style.mentionSuggestionsBackgroundColor) } @@ -106,6 +112,22 @@ public class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, Mess } } +/** + * Adapter used to render mention suggestions. + */ +public interface MentionSuggestionsAdapter { + + /** + * Sets the list of mention suggestions to be displayed. + */ + public fun setItems(items: List) + + /** + * Returns the number of items in the adapter. + */ + public fun getItemCount(): Int +} + /** * [RecyclerView.Adapter] responsible for displaying mention suggestions in a RecyclerView. * @@ -115,7 +137,7 @@ public class DefaultMessageComposerMentionSuggestionsContent : FrameLayout, Mess private class MentionsAdapter( private val style: MessageComposerViewStyle, private inline val mentionSelectionListener: (User) -> Unit, -) : SimpleListAdapter() { +) : SimpleListAdapter(), MentionSuggestionsAdapter { /** * Creates and instantiates a new instance of [MentionsViewHolder]. diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent.kt index 50df84a73d3..6b5eeeed0be 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/composer/content/DefaultMessageComposerTrailingContent.kt @@ -39,21 +39,21 @@ import io.getstream.chat.android.ui.message.composer.MessageComposerViewStyle * Represents the default content shown at the end of [MessageComposerView]. */ @ExperimentalStreamChatApi -public class DefaultMessageComposerTrailingContent : FrameLayout, MessageComposerContent { +public open class DefaultMessageComposerTrailingContent : FrameLayout, MessageComposerContent { /** * Generated binding class for the XML layout. */ - private lateinit var binding: StreamUiMessageComposerDefaultTrailingContentBinding + protected lateinit var binding: StreamUiMessageComposerDefaultTrailingContentBinding /** * The style for [MessageComposerView]. */ - private lateinit var style: MessageComposerViewStyle + protected lateinit var style: MessageComposerViewStyle /** * Click listener for the send message button. */ - public var sendMessageButtonClickListener: () -> Unit = {} + public var sendMessageButtonClickListener: (() -> Unit)? = null public constructor(context: Context) : this(context, null) @@ -72,7 +72,7 @@ public class DefaultMessageComposerTrailingContent : FrameLayout, MessageCompose */ private fun init() { binding = StreamUiMessageComposerDefaultTrailingContentBinding.inflate(streamThemeInflater, this) - binding.sendMessageButton.setOnClickListener { sendMessageButtonClickListener() } + binding.sendMessageButton.setOnClickListener { sendMessageButtonClickListener?.invoke() } } /**