From d8bfc05bf3574997040d481095bc17fdd772f74d Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Thu, 14 May 2026 16:40:48 +0200 Subject: [PATCH 1/3] Allow voters to suggest poll options in UI Components --- .../api/stream-chat-android-ui-common.api | 1 + .../messages/list/MessageListController.kt | 12 ++- .../list/MessageListControllerTests.kt | 21 ++++ .../api/stream-chat-android-ui-components.api | 37 ++++++- .../feature/messages/list/MessageListView.kt | 27 ++++++ .../adapter/MessageListListenerContainer.kt | 2 + .../MessageListListenerContainerImpl.kt | 10 ++ .../list/adapter/view/PollViewStyle.kt | 21 ++++ .../list/adapter/view/internal/PollView.kt | 29 ++++++ .../adapter/viewholder/impl/PollViewHolder.kt | 3 + .../poll/SuggestPollOptionDialogFragment.kt | 97 +++++++++++++++++++ .../messages/MessageListViewModel.kt | 12 +++ .../messages/MessageListViewModelBinding.kt | 11 +++ .../stream_ui_dialog_suggest_poll_option.xml | 32 ++++++ .../stream_ui_item_poll_suggest_option.xml | 37 +++++++ .../src/main/res/values/attrs_poll_view.xml | 10 ++ .../src/main/res/values/strings.xml | 3 + 17 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt create mode 100644 stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_suggest_poll_option.xml create mode 100644 stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_poll_suggest_option.xml diff --git a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api index b07b1c8fa00..9cc2f97b9c6 100644 --- a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api +++ b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api @@ -732,6 +732,7 @@ public final class io/getstream/chat/android/ui/common/feature/messages/list/Mes public fun (Ljava/lang/String;Lio/getstream/chat/android/ui/common/helper/ClipboardHandler;ZLjava/lang/String;Ljava/lang/String;ILio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;Lkotlinx/coroutines/flow/StateFlow;Lio/getstream/chat/android/ui/common/state/messages/list/DeletedMessageVisibility;ZLio/getstream/chat/android/ui/common/state/messages/list/MessageFooterVisibility;ZLio/getstream/chat/android/ui/common/feature/messages/list/DateSeparatorHandler;Lio/getstream/chat/android/ui/common/feature/messages/list/DateSeparatorHandler;Lio/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler;ZZ)V public synthetic fun (Ljava/lang/String;Lio/getstream/chat/android/ui/common/helper/ClipboardHandler;ZLjava/lang/String;Ljava/lang/String;ILio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;Lkotlinx/coroutines/flow/StateFlow;Lio/getstream/chat/android/ui/common/state/messages/list/DeletedMessageVisibility;ZLio/getstream/chat/android/ui/common/state/messages/list/MessageFooterVisibility;ZLio/getstream/chat/android/ui/common/feature/messages/list/DateSeparatorHandler;Lio/getstream/chat/android/ui/common/feature/messages/list/DateSeparatorHandler;Lio/getstream/chat/android/ui/common/feature/messages/list/MessagePositionHandler;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addPollOption (Lio/getstream/chat/android/models/Poll;Ljava/lang/String;)V + public final fun addPollOption (Ljava/lang/String;Ljava/lang/String;)V public final fun banUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V public static synthetic fun banUser$default (Lio/getstream/chat/android/ui/common/feature/messages/list/MessageListController;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)V public final fun blockUser (Ljava/lang/String;)V diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt index d821ca31f36..7b17e6b38c7 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListController.kt @@ -2447,8 +2447,18 @@ public class MessageListController( * @param option The text of the new option to be added. */ public fun addPollOption(poll: Poll, option: String) { + addPollOption(pollId = poll.id, option = option) + } + + /** + * Creates a new poll option for the poll identified by [pollId]. + * + * @param pollId The id of the poll to which the option will be added. + * @param option The text of the new option to be added. + */ + public fun addPollOption(pollId: String, option: String) { scope.launch { - chatClient.createPollOption(poll.id, PollOption(text = option)).await() + chatClient.createPollOption(pollId, PollOption(text = option)).await() } } diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt index 2dccad09c87..cbf5d81ca46 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/list/MessageListControllerTests.kt @@ -35,6 +35,7 @@ import io.getstream.chat.android.models.Member import io.getstream.chat.android.models.Message import io.getstream.chat.android.models.MessageType import io.getstream.chat.android.models.MessagesState +import io.getstream.chat.android.models.PollOption import io.getstream.chat.android.models.Reaction import io.getstream.chat.android.models.SyncStatus import io.getstream.chat.android.models.TypingEvent @@ -49,6 +50,7 @@ import io.getstream.chat.android.randomMembers import io.getstream.chat.android.randomMessage import io.getstream.chat.android.randomMessageList import io.getstream.chat.android.randomOption +import io.getstream.chat.android.randomPoll import io.getstream.chat.android.randomPollVote import io.getstream.chat.android.randomReaction import io.getstream.chat.android.randomString @@ -994,6 +996,21 @@ internal class MessageListControllerTests { controller.errorEvents.value `should be equal to` expectedEvent } + @Test + fun `When calling addPollOption, ChatClient createPollOption is invoked`() = runTest { + val poll = randomPoll() + val optionText = randomString() + val chatClient = mock() + val controller = Fixture(chatClient = chatClient) + .givenCurrentUser() + .givenChannelState(messagesState = MutableStateFlow(emptyList())) + .givenCreatePollOption(callFrom { PollOption(text = optionText) }) + .get() + controller.addPollOption(poll = poll, option = optionText) + + verify(chatClient).createPollOption(poll.id, PollOption(text = optionText)) + } + @Test fun `When toggleOriginalText, the message translation is toggled`() = runTest { val messageId = randomString() @@ -1291,6 +1308,10 @@ internal class MessageListControllerTests { whenever(chatClient.removePollVote(any(), any(), voteId = any())) doReturn vote } + fun givenCreatePollOption(option: Call) = apply { + whenever(chatClient.createPollOption(any(), any())) doReturn option + } + fun givenSendReaction(reaction: Call) = apply { whenever(chatClient.sendReaction(any(), any(), any(), any())) doReturn reaction } 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 39709ebaf34..95e9445b673 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 @@ -2353,6 +2353,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/MessageLis public final fun setOnReactionViewClickListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnReactionViewClickListener;)V public final fun setOnReplyMessageClickListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnReplyMessageClickListener;)V public final fun setOnScrollToBottomHandler (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnScrollToBottomHandler;)V + public final fun setOnSuggestPollOptionClickListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnSuggestPollOptionClickListener;)V public final fun setOnThreadClickListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnThreadClickListener;)V public final fun setOnUnreadLabelClickListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnUnreadLabelClickListener;)V public final fun setOnUnreadLabelReachedListener (Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnUnreadLabelReachedListener;)V @@ -2600,6 +2601,10 @@ public abstract interface class io/getstream/chat/android/ui/feature/messages/li public abstract fun onShowAllPollOptionClick (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Poll;)Z } +public abstract interface class io/getstream/chat/android/ui/feature/messages/list/MessageListView$OnSuggestPollOptionClickListener { + public abstract fun onSuggestPollOptionClick (Lio/getstream/chat/android/models/Poll;)Z +} + public abstract interface class io/getstream/chat/android/ui/feature/messages/list/MessageListView$OnThreadClickListener { public abstract fun onThreadClick (Lio/getstream/chat/android/models/Message;)Z } @@ -3153,6 +3158,7 @@ public abstract interface class io/getstream/chat/android/ui/feature/messages/li public abstract fun getOnPollCloseClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnPollCloseClickListener; public abstract fun getOnPollOptionClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnPollOptionClickListener; public abstract fun getOnShowAllPollOptionClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnShowAllPollOptionClickListener; + public abstract fun getOnSuggestPollOptionClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnSuggestPollOptionClickListener; public abstract fun getOnViewPollResultClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnViewPollResultClickListener; public abstract fun getReactionViewClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnReactionViewClickListener; public abstract fun getThreadClickListener ()Lio/getstream/chat/android/ui/feature/messages/list/MessageListView$OnThreadClickListener; @@ -3233,7 +3239,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/vi } public final class io/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle : io/getstream/chat/android/ui/helper/ViewStyle { - public fun (Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;)V + public fun (Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;)V public final fun component1 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component2 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component3 ()Landroid/graphics/drawable/Drawable; @@ -3242,8 +3248,9 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/vi public final fun component6 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component7 ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun component8 ()Lio/getstream/chat/android/ui/font/TextStyle; - public final fun copy (Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle; - public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;ILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle; + public final fun component9 ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun copy (Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Landroid/graphics/drawable/Drawable;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;Lio/getstream/chat/android/ui/font/TextStyle;ILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle; public fun equals (Ljava/lang/Object;)Z public final fun getPollCloseTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getPollOptionCheckDrawable ()Landroid/graphics/drawable/Drawable; @@ -3252,6 +3259,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/adapter/vi public final fun getPollResultsTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getPollShowAllOptionsTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getPollSubtitleTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; + public final fun getPollSuggestOptionTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public final fun getPollTitleTextStyle ()Lio/getstream/chat/android/ui/font/TextStyle; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -3534,6 +3542,16 @@ public final class io/getstream/chat/android/ui/feature/messages/list/internal/p public final fun newInstance (Lio/getstream/chat/android/models/Poll;)Lio/getstream/chat/android/ui/feature/messages/list/internal/poll/PollResultsDialogFragment; } +public final class io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment : androidx/appcompat/app/AppCompatDialogFragment { + public static final field Companion Lio/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment$Companion; + public fun ()V + public fun onCreateDialog (Landroid/os/Bundle;)Landroid/app/Dialog; +} + +public final class io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment$Companion { + public final fun newInstance (Ljava/lang/String;)Lio/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment; +} + public class io/getstream/chat/android/ui/feature/messages/list/options/message/DefaultMessageOptionItemsFactory : io/getstream/chat/android/ui/feature/messages/list/options/message/MessageOptionItemsFactory { public fun (Landroid/content/Context;)V public fun createMessageOptionItems (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;ZLjava/util/Set;Lio/getstream/chat/android/ui/feature/messages/list/MessageListViewStyle;)Ljava/util/List; @@ -4983,6 +5001,19 @@ public final class io/getstream/chat/android/ui/viewmodel/messages/MessageListVi public fun toString ()Ljava/lang/String; } +public final class io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event$PollOptionSuggested : io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event$PollOptionSuggested; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event$PollOptionSuggested;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event$PollOptionSuggested; + public fun equals (Ljava/lang/Object;)Z + public final fun getOption ()Ljava/lang/String; + public final fun getPollId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event$PollOptionUpdated : io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel$Event { public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Poll;Lio/getstream/chat/android/models/Option;)V public final fun component1 ()Lio/getstream/chat/android/models/Message; diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt index 1daea42a384..69374d296da 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListView.kt @@ -93,6 +93,7 @@ import io.getstream.chat.android.ui.feature.messages.list.internal.SwipeReplyCal import io.getstream.chat.android.ui.feature.messages.list.internal.canReplyToMessage import io.getstream.chat.android.ui.feature.messages.list.internal.poll.AllPollOptionsDialogFragment import io.getstream.chat.android.ui.feature.messages.list.internal.poll.PollResultsDialogFragment +import io.getstream.chat.android.ui.feature.messages.list.internal.poll.SuggestPollOptionDialogFragment import io.getstream.chat.android.ui.feature.messages.list.options.message.MessageOptionItem import io.getstream.chat.android.ui.feature.messages.list.options.message.MessageOptionItemsFactory import io.getstream.chat.android.ui.feature.messages.list.options.message.MessageOptionsDialogFragment @@ -615,6 +616,13 @@ public class MessageListView : ConstraintLayout { true } ?: false } + private val defaultOnSuggestPollOptionClickListener = OnSuggestPollOptionClickListener { poll -> + context.getFragmentManager()?.let { fragmentManager -> + SuggestPollOptionDialogFragment.newInstance(poll.id) + .show(fragmentManager, SuggestPollOptionDialogFragment.TAG) + true + } ?: false + } private val listenerContainer = MessageListListenerContainerImpl( messageClickListener = defaultMessageClickListener, @@ -632,6 +640,7 @@ public class MessageListView : ConstraintLayout { onShowAllPollOptionClickListener = defaultOnShowAllPollOptionClickListener, onPollCloseClickListener = defaultOnPollCloseClickListener, onViewPollResultClickListener = defaultOnViewPollResultClickListener, + onSuggestPollOptionClickListener = defaultOnSuggestPollOptionClickListener, ) private var enterThreadListener = defaultEnterThreadListener private var userReactionClickListener = defaultUserReactionClickListener @@ -1433,6 +1442,20 @@ public class MessageListView : ConstraintLayout { } } + /** + * Set the Suggest Poll Option click listener to be used by MessageListView. + * + * @param listener The listener to use. If null, the default will be used instead. + */ + public fun setOnSuggestPollOptionClickListener(listener: OnSuggestPollOptionClickListener?) { + listenerContainer.onSuggestPollOptionClickListener = + if (listener == null) { + defaultOnSuggestPollOptionClickListener + } else { + OnSuggestPollOptionClickListener(listener::onSuggestPollOptionClick) + } + } + /** * Sets the message long click listener to be used by MessageListView. * @@ -2311,6 +2334,10 @@ public class MessageListView : ConstraintLayout { public fun onViewPollResultClick(poll: Poll): Boolean } + public fun interface OnSuggestPollOptionClickListener { + public fun onSuggestPollOptionClick(poll: Poll): Boolean + } + @Deprecated( message = "Use ReplyMessageClickListener instead", replaceWith = ReplaceWith("ReplyMessageClickListener"), diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainer.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainer.kt index aa879210e09..5261ad7e2eb 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainer.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainer.kt @@ -35,6 +35,7 @@ import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPoll import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPollOptionClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnReactionViewClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnShowAllPollOptionClickListener +import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnSuggestPollOptionClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnThreadClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnTranslatedLabelClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnUnreadLabelReachedListener @@ -80,6 +81,7 @@ public sealed interface MessageListListeners { public val onShowAllPollOptionClickListener: OnShowAllPollOptionClickListener public val onPollCloseClickListener: OnPollCloseClickListener public val onViewPollResultClickListener: OnViewPollResultClickListener + public val onSuggestPollOptionClickListener: OnSuggestPollOptionClickListener } @Deprecated( diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainerImpl.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainerImpl.kt index 02da0485e23..e8a84659dbf 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainerImpl.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/MessageListListenerContainerImpl.kt @@ -28,6 +28,7 @@ import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPoll import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnPollOptionClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnReactionViewClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnShowAllPollOptionClickListener +import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnSuggestPollOptionClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnThreadClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnTranslatedLabelClickListener import io.getstream.chat.android.ui.feature.messages.list.MessageListView.OnUnreadLabelReachedListener @@ -57,6 +58,7 @@ internal class MessageListListenerContainerImpl( onShowAllPollOptionClickListener: OnShowAllPollOptionClickListener, onPollCloseClickListener: OnPollCloseClickListener, onViewPollResultClickListener: OnViewPollResultClickListener, + onSuggestPollOptionClickListener: OnSuggestPollOptionClickListener, ) : MessageListListeners { private object EmptyFunctions { val ONE_PARAM: (Any) -> Boolean = { _ -> false } @@ -198,4 +200,12 @@ internal class MessageListListenerContainerImpl( realListener().onViewPollResultClick(poll) } } + + override var onSuggestPollOptionClickListener: OnSuggestPollOptionClickListener by ListenerDelegate( + onSuggestPollOptionClickListener, + ) { realListener -> + OnSuggestPollOptionClickListener { poll -> + realListener().onSuggestPollOptionClick(poll) + } + } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle.kt index 54d51dfced9..b5156ec0404 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/PollViewStyle.kt @@ -38,6 +38,7 @@ public data class PollViewStyle( public val pollCloseTextStyle: TextStyle, public val pollResultsTextStyle: TextStyle, public val pollShowAllOptionsTextStyle: TextStyle, + public val pollSuggestOptionTextStyle: TextStyle, ) : ViewStyle { internal companion object { @@ -184,6 +185,25 @@ public data class PollViewStyle( Typeface.NORMAL, ) .build() + + val pollSuggestOptionTextStyle = TextStyle.Builder(a) + .size( + R.styleable.PollView_streamUiPollSuggestOptionTextSize, + context.getDimension(R.dimen.stream_ui_text_large), + ) + .color( + R.styleable.PollView_streamUiPollSuggestOptionTextColor, + context.getColorCompat(R.color.stream_ui_accent_blue), + ) + .font( + R.styleable.PollView_streamUiPollSuggestOptionFontAssets, + R.styleable.PollView_streamUiPollSuggestOptionTextFont, + ) + .style( + R.styleable.PollView_streamUiPollSuggestOptionTextStyle, + Typeface.NORMAL, + ) + .build() return PollViewStyle( pollTitleTextStyle = pollTitleTextStyle, pollSubtitleTextStyle = pollSubtitleTextStyle, @@ -193,6 +213,7 @@ public data class PollViewStyle( pollCloseTextStyle = pollCloseTextStyle, pollResultsTextStyle = pollResultsTextStyle, pollShowAllOptionsTextStyle = pollShowAllOptionsTextStyle, + pollSuggestOptionTextStyle = pollSuggestOptionTextStyle, ).let(TransformStyle.pollViewStyleTransformer::transform) } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/PollView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/PollView.kt index 79f850b4fda..98d336a6c92 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/PollView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/view/internal/PollView.kt @@ -39,6 +39,7 @@ import io.getstream.chat.android.ui.databinding.StreamUiItemPollCloseBinding import io.getstream.chat.android.ui.databinding.StreamUiItemPollHeaderBinding import io.getstream.chat.android.ui.databinding.StreamUiItemPollResultsBinding import io.getstream.chat.android.ui.databinding.StreamUiItemPollShowAllOptionsBinding +import io.getstream.chat.android.ui.databinding.StreamUiItemPollSuggestOptionBinding import io.getstream.chat.android.ui.feature.messages.list.adapter.view.PollViewStyle import io.getstream.chat.android.ui.font.setTextStyle import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper @@ -56,6 +57,7 @@ internal class PollView : RecyclerView { var onClosePollClick: ((Poll) -> Unit) = { _ -> } var onViewPollResultsClick: ((Poll) -> Unit) = { _ -> } var onShowAllPollOptionClick: (() -> Unit) = { } + var onSuggestOptionClick: ((Poll) -> Unit) = { _ -> } constructor(context: Context) : this(context, null, 0) @@ -87,6 +89,7 @@ internal class PollView : RecyclerView { onClosePollClick = { onClosePollClick(poll) }, onViewPollResultsClick = { onViewPollResultsClick(poll) }, onShowAllOptionsClick = { onShowAllPollOptionClick() }, + onSuggestOptionClick = { onSuggestOptionClick(poll) }, ) adapter = pollAdapter } @@ -121,6 +124,9 @@ internal class PollView : RecyclerView { .takeIf { poll.options.size > PollsConstants.MIN_NUMBER_OF_VISIBLE_OPTIONS } ?.let(pollItems::add) + PollItem.SuggestOption.takeIf { poll.allowUserSuggestedOptions && !poll.closed } + ?.let(pollItems::add) + pollItems.add(PollItem.ViewResults) PollItem.Close.takeIf { isMine && !poll.closed } @@ -136,6 +142,7 @@ private class PollAdapter( private val onClosePollClick: () -> Unit, private val onViewPollResultsClick: () -> Unit, private val onShowAllOptionsClick: () -> Unit, + private val onSuggestOptionClick: () -> Unit, ) : ListAdapter>(PollItemDiffCallback) { companion object { @@ -144,6 +151,7 @@ private class PollAdapter( private const val VIEW_TYPE_CLOSE = 3 private const val VIEW_TYPE_RESULTS = 4 private const val VIEW_TYPE_SHOW_ALL_OPTIONS = 5 + private const val VIEW_TYPE_SUGGEST_OPTION = 6 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollItemViewHolder { @@ -177,6 +185,12 @@ private class PollAdapter( onShowAllOptionsClick, ) + VIEW_TYPE_SUGGEST_OPTION -> SuggestOptionViewHolder( + StreamUiItemPollSuggestOptionBinding.inflate(parent.streamThemeInflater, parent, false) + .applyStyle(pollViewStyle), + onSuggestOptionClick, + ) + else -> throw IllegalArgumentException("Unknown view type: $viewType") } } @@ -187,6 +201,7 @@ private class PollAdapter( PollItem.Close -> VIEW_TYPE_CLOSE PollItem.ViewResults -> VIEW_TYPE_RESULTS is PollItem.ShowAllOptions -> VIEW_TYPE_SHOW_ALL_OPTIONS + PollItem.SuggestOption -> VIEW_TYPE_SUGGEST_OPTION } override fun onBindViewHolder(holder: PollItemViewHolder, position: Int) { @@ -224,6 +239,7 @@ private sealed class PollItem { data object Close : PollItem() data class ShowAllOptions(val count: Int) : PollItem() data object ViewResults : PollItem() + data object SuggestOption : PollItem() } private sealed class PollItemViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { @@ -334,6 +350,15 @@ private class ViewResultsViewHolder( } } +private class SuggestOptionViewHolder( + private val binding: StreamUiItemPollSuggestOptionBinding, + private val onSuggestOptionClick: () -> Unit, +) : PollItemViewHolder(binding) { + override fun bind(pollItem: PollItem.SuggestOption) { + binding.root.setOnClickListener { onSuggestOptionClick() } + } +} + private fun StreamUiItemPollHeaderBinding.applyStyle(style: PollViewStyle) = this.apply { title.setTextStyle(style.pollTitleTextStyle) subtitle.setTextStyle(style.pollSubtitleTextStyle) @@ -356,3 +381,7 @@ private fun StreamUiItemPollResultsBinding.applyStyle(style: PollViewStyle) = th private fun StreamUiItemPollShowAllOptionsBinding.applyStyle(style: PollViewStyle) = this.apply { pollShowAllOptions.setTextStyle(style.pollShowAllOptionsTextStyle) } + +private fun StreamUiItemPollSuggestOptionBinding.applyStyle(style: PollViewStyle) = this.apply { + pollSuggestOption.setTextStyle(style.pollSuggestOptionTextStyle) +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/PollViewHolder.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/PollViewHolder.kt index 5bf576ca0a1..eb318ca1912 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/PollViewHolder.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/viewholder/impl/PollViewHolder.kt @@ -56,6 +56,9 @@ public class PollViewHolder( binding.pollView.onShowAllPollOptionClick = { messageListListeners?.onShowAllPollOptionClickListener?.onShowAllPollOptionClick(data.message, poll) } + binding.pollView.onSuggestOptionClick = { + messageListListeners?.onSuggestPollOptionClickListener?.onSuggestPollOptionClick(poll) + } } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt new file mode 100644 index 00000000000..46d1f99b192 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014-2026 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.feature.messages.list.internal.poll + +import android.app.Dialog +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.WindowManager +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.core.os.bundleOf +import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.databinding.StreamUiDialogSuggestPollOptionBinding +import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper +import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater + +/** + * Dialog that lets a voter suggest a new option for a poll. + * + * Delivers the entered option via the fragment-result API under [REQUEST_KEY], with + * the poll id in [BUNDLE_KEY_POLL_ID] and the trimmed option text in [BUNDLE_KEY_OPTION_TEXT]. + */ +public class SuggestPollOptionDialogFragment : AppCompatDialogFragment() { + + private val pollId: String + get() = requireArguments().getString(ARG_POLL_ID) + ?: error("Poll ID not found in arguments") + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val binding = StreamUiDialogSuggestPollOptionBinding.inflate(requireContext().streamThemeInflater) + + val dialog = AlertDialog.Builder(requireContext().createStreamThemeWrapper()) + .setTitle(R.string.stream_ui_poll_suggest_option_dialog_title) + .setView(binding.root) + .setPositiveButton(R.string.stream_ui_poll_suggest_option_dialog_submit) { _, _ -> + val text = binding.optionInput.text?.toString()?.trim().orEmpty() + if (text.isNotEmpty()) { + parentFragmentManager.setFragmentResult( + REQUEST_KEY, + bundleOf( + BUNDLE_KEY_POLL_ID to pollId, + BUNDLE_KEY_OPTION_TEXT to text, + ), + ) + } + } + .setNegativeButton(R.string.stream_ui_poll_suggest_option_dialog_cancel, null) + .create() + + dialog.setOnShowListener { + val confirm = dialog.getButton(AlertDialog.BUTTON_POSITIVE) + confirm.isEnabled = false + binding.optionInput.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + override fun afterTextChanged(s: Editable?) { + confirm.isEnabled = s?.toString()?.trim()?.isNotEmpty() == true + } + }, + ) + binding.optionInput.requestFocus() + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + } + + return dialog + } + + public companion object { + internal const val TAG: String = "SuggestPollOptionDialogFragment" + internal const val REQUEST_KEY: String = "stream_ui_suggest_poll_option_request" + internal const val BUNDLE_KEY_POLL_ID: String = "stream_ui_suggest_poll_option_poll_id" + internal const val BUNDLE_KEY_OPTION_TEXT: String = "stream_ui_suggest_poll_option_text" + + private const val ARG_POLL_ID: String = "arg_poll_id" + + public fun newInstance(pollId: String): SuggestPollOptionDialogFragment = + SuggestPollOptionDialogFragment().apply { + arguments = bundleOf(ARG_POLL_ID to pollId) + } + } +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel.kt index 4c0cd6a5fd1..ec5487b7961 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModel.kt @@ -306,6 +306,10 @@ public class MessageListViewModel( option = event.option, ) is Event.PollClosed -> messageListController.closePoll(event.poll) + is Event.PollOptionSuggested -> messageListController.addPollOption( + pollId = event.pollId, + option = event.option, + ) } } @@ -831,5 +835,13 @@ public class MessageListViewModel( * @param poll The poll to be closed. */ public data class PollClosed(val poll: Poll) : Event() + + /** + * When the user suggests a new option for a poll. + * + * @param pollId The id of the poll to add the option to. + * @param option The text of the option suggested by the user. + */ + public data class PollOptionSuggested(val pollId: String, val option: String) : Event() } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelBinding.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelBinding.kt index a648a362a12..f550b46e5b1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelBinding.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/messages/MessageListViewModelBinding.kt @@ -26,7 +26,9 @@ import io.getstream.chat.android.state.utils.EventObserver import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.feature.gallery.toAttachment import io.getstream.chat.android.ui.feature.messages.list.MessageListView +import io.getstream.chat.android.ui.feature.messages.list.internal.poll.SuggestPollOptionDialogFragment import io.getstream.chat.android.ui.utils.PermissionChecker +import io.getstream.chat.android.ui.utils.extensions.getFragmentManager import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel.Event.BottomEndRegionReached import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel.Event.DeleteMessage import io.getstream.chat.android.ui.viewmodel.messages.MessageListViewModel.Event.DownloadAttachment @@ -189,4 +191,13 @@ public fun MessageListViewModel.bindView( view.setOnPollCloseClickListener { poll: Poll -> true.also { onEvent(MessageListViewModel.Event.PollClosed(poll)) } } + view.context.getFragmentManager() + ?.setFragmentResultListener(SuggestPollOptionDialogFragment.REQUEST_KEY, lifecycleOwner) { _, bundle -> + val pollId = + bundle.getString(SuggestPollOptionDialogFragment.BUNDLE_KEY_POLL_ID) ?: return@setFragmentResultListener + val optionText = bundle.getString(SuggestPollOptionDialogFragment.BUNDLE_KEY_OPTION_TEXT)?.trim() + ?.takeIf(String::isNotEmpty) + ?: return@setFragmentResultListener + onEvent(MessageListViewModel.Event.PollOptionSuggested(pollId, optionText)) + } } diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_suggest_poll_option.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_suggest_poll_option.xml new file mode 100644 index 00000000000..d8c696e3a06 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_dialog_suggest_poll_option.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_poll_suggest_option.xml b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_poll_suggest_option.xml new file mode 100644 index 00000000000..bb43e9599f9 --- /dev/null +++ b/stream-chat-android-ui-components/src/main/res/layout/stream_ui_item_poll_suggest_option.xml @@ -0,0 +1,37 @@ + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_poll_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_poll_view.xml index 644b6382665..9f6d2d8a6d2 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_poll_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_poll_view.xml @@ -125,5 +125,15 @@ + + + + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/values/strings.xml b/stream-chat-android-ui-components/src/main/res/values/strings.xml index d2417b1f46b..20c380b4898 100644 --- a/stream-chat-android-ui-components/src/main/res/values/strings.xml +++ b/stream-chat-android-ui-components/src/main/res/values/strings.xml @@ -203,6 +203,9 @@ An error occurred while loading the poll results. Please try again later. Show All An error occurred while loading the poll option results. Please try again later. + Suggest an option + Confirm + Dismiss Thread Reply Channel without name From c47fe905ff9b409c09608606d9e8df21774aba45 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Tue, 19 May 2026 11:07:13 +0200 Subject: [PATCH 2/3] Use doAfterTextChanged --- .../poll/SuggestPollOptionDialogFragment.kt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt index 46d1f99b192..f01fbdc6a2c 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt @@ -18,12 +18,11 @@ package io.getstream.chat.android.ui.feature.messages.list.internal.poll import android.app.Dialog import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher import android.view.WindowManager import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment import androidx.core.os.bundleOf +import androidx.core.widget.doAfterTextChanged import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiDialogSuggestPollOptionBinding import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper @@ -65,15 +64,9 @@ public class SuggestPollOptionDialogFragment : AppCompatDialogFragment() { dialog.setOnShowListener { val confirm = dialog.getButton(AlertDialog.BUTTON_POSITIVE) confirm.isEnabled = false - binding.optionInput.addTextChangedListener( - object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) { - confirm.isEnabled = s?.toString()?.trim()?.isNotEmpty() == true - } - }, - ) + binding.optionInput.doAfterTextChanged { editable -> + confirm.isEnabled = editable?.toString()?.trim()?.isNotEmpty() == true + } binding.optionInput.requestFocus() dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) } From 6bd28b6a115e1902c55daa9d837ce484ad6b9f2d Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Tue, 19 May 2026 11:08:26 +0200 Subject: [PATCH 3/3] Align Compose & XML option name trimming --- .../list/internal/poll/SuggestPollOptionDialogFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt index f01fbdc6a2c..ad890344ea2 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/internal/poll/SuggestPollOptionDialogFragment.kt @@ -32,7 +32,7 @@ import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater * Dialog that lets a voter suggest a new option for a poll. * * Delivers the entered option via the fragment-result API under [REQUEST_KEY], with - * the poll id in [BUNDLE_KEY_POLL_ID] and the trimmed option text in [BUNDLE_KEY_OPTION_TEXT]. + * the poll id in [BUNDLE_KEY_POLL_ID] and the option text in [BUNDLE_KEY_OPTION_TEXT]. */ public class SuggestPollOptionDialogFragment : AppCompatDialogFragment() { @@ -47,8 +47,8 @@ public class SuggestPollOptionDialogFragment : AppCompatDialogFragment() { .setTitle(R.string.stream_ui_poll_suggest_option_dialog_title) .setView(binding.root) .setPositiveButton(R.string.stream_ui_poll_suggest_option_dialog_submit) { _, _ -> - val text = binding.optionInput.text?.toString()?.trim().orEmpty() - if (text.isNotEmpty()) { + val text = binding.optionInput.text?.toString().orEmpty() + if (text.isNotBlank()) { parentFragmentManager.setFragmentResult( REQUEST_KEY, bundleOf(