From 90d3b1d2f96ed9c0534ba6f0378bf574d4a8cec7 Mon Sep 17 00:00:00 2001 From: Ryan Hurst Date: Thu, 7 May 2026 16:25:37 -0600 Subject: [PATCH 1/2] Add configurable character limits for poll creation Add optional titleCharLimit and answerCharLimit parameters to CreatePollDialogFragment.newInstance() to allow developers to enforce character limits on poll titles and answer options during poll creation. The limits are passed via Bundle arguments and applied using InputFilter.LengthFilter. Both parameters default to null, meaning no character limit is enforced by default, maintaining backward compatibility. Usage: CreatePollDialogFragment.newInstance( listener, titleCharLimit = 100, answerCharLimit = 50 ) --- .../picker/poll/CreatePollDialogFragment.kt | 28 ++++++++++++++++--- .../attachment/picker/poll/OptionsAdapter.kt | 10 +++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt index a1a9ebaf5d9..0d6c5104147 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt @@ -17,6 +17,7 @@ package io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.poll import android.os.Bundle +import android.text.InputFilter import android.view.LayoutInflater import android.view.MenuItem import android.view.View @@ -48,7 +49,10 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { private var createPollDialogListener: CreatePollDialogListener? = null private val createPollViewModel: CreatePollViewModel by viewModels() private val optionsAdapter: OptionsAdapter by lazy { - OptionsAdapter { id, text -> createPollViewModel.onOptionTextChanged(id, text) } + OptionsAdapter( + answerCharLimit = arguments?.getInt(ARG_ANSWER_CHAR_LIMIT)?.takeIf { it > 0 }, + onOptionChange = { id, text -> createPollViewModel.onOptionTextChanged(id, text) }, + ) } private lateinit var sendMenuItem: MenuItem @@ -81,6 +85,9 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { */ private fun setupDialog() { setupToolbar(binding.toolbar) + arguments?.getInt(ARG_TITLE_CHAR_LIMIT)?.takeIf { it > 0 }?.let { limit -> + binding.question.filters = arrayOf(InputFilter.LengthFilter(limit)) + } binding.multipleAnswersSwitch.setOnCheckedChangeListener { _, isChecked -> binding.multipleAnswersCount.isVisible = isChecked createPollViewModel.setAllowMultipleVotes(isChecked) @@ -158,15 +165,28 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { public companion object { public const val TAG: String = "create_poll_dialog_fragment" + private const val ARG_TITLE_CHAR_LIMIT: String = "arg_title_char_limit" + private const val ARG_ANSWER_CHAR_LIMIT: String = "arg_answer_char_limit" /** * Creates a new instance of [CreatePollDialogFragment]. * + * @param createPollDialogListener The listener for poll creation events. + * @param titleCharLimit Optional character limit for the poll title. Null means no limit. + * @param answerCharLimit Optional character limit for poll answer options. Null means no limit. * @return A new instance of [CreatePollDialogFragment]. */ - public fun newInstance(createPollDialogListener: CreatePollDialogListener): CreatePollDialogFragment { - return CreatePollDialogFragment() - .setCreatePollDialogListener(createPollDialogListener) + public fun newInstance( + createPollDialogListener: CreatePollDialogListener, + titleCharLimit: Int? = null, + answerCharLimit: Int? = null, + ): CreatePollDialogFragment { + return CreatePollDialogFragment().apply { + arguments = Bundle().apply { + titleCharLimit?.let { putInt(ARG_TITLE_CHAR_LIMIT, it) } + answerCharLimit?.let { putInt(ARG_ANSWER_CHAR_LIMIT, it) } + } + }.setCreatePollDialogListener(createPollDialogListener) } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt index f6430e3b2a1..04183426a7a 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt @@ -17,6 +17,7 @@ package io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.poll import android.text.Editable +import android.text.InputFilter import android.text.TextWatcher import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil @@ -27,6 +28,7 @@ import io.getstream.chat.android.ui.databinding.StreamUiPollOptionBinding import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater public class OptionsAdapter( + private val answerCharLimit: Int?, private val onOptionChange: (id: Int, text: String) -> Unit, ) : ListAdapter(OptionDiffCallback) { @@ -39,6 +41,7 @@ public class OptionsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder = OptionViewHolder( parent = parent, + answerCharLimit = answerCharLimit, onOptionChange = onOptionChange, ) @@ -53,11 +56,18 @@ public class OptionsAdapter( parent, false, ), + private val answerCharLimit: Int?, private val onOptionChange: (id: Int, text: String) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { private lateinit var pollAnswer: PollAnswer + init { + answerCharLimit?.let { limit -> + binding.option.filters = arrayOf(InputFilter.LengthFilter(limit)) + } + } + private val textWatcher = object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { /* no-op */ } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { /* no-op */ } From 16f339e3b0897260a20ea16772c869abde833fb0 Mon Sep 17 00:00:00 2001 From: Ryan Hurst Date: Mon, 11 May 2026 12:41:36 -0400 Subject: [PATCH 2/2] Address PR feedback for poll character limits feature Improvements: - Rename parameters for better alignment with class naming: * titleCharLimit -> questionTextLimit * answerCharLimit -> optionTextLimit - Add @JvmOverloads annotation to CreatePollDialogFragment.newInstance() to prevent breaking changes for Java integrations - Add secondary constructor to OptionsAdapter for backward compatibility, maintaining original signature without optionTextLimit parameter This ensures the API remains backward compatible for both Kotlin and Java consumers while adding the new character limit functionality. --- .../picker/poll/CreatePollDialogFragment.kt | 32 ++++++++++++------- .../attachment/picker/poll/OptionsAdapter.kt | 15 ++++++--- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt index 0d6c5104147..a51bfe96716 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt @@ -40,7 +40,16 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch /** - * Represent the bottom sheet dialog that allows users to pick attachments. + * A dialog fragment that allows users to create polls with configurable character limits. + * + * This dialog provides a UI for creating polls with customizable settings including: + * - Poll question with optional character limit + * - Multiple answer options with optional character limit + * - Multiple vote configuration + * - Anonymous poll option + * - Option suggestion capability + * + * Use [newInstance] to create an instance with optional character limits. */ public class CreatePollDialogFragment : AppCompatDialogFragment() { @@ -50,7 +59,7 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { private val createPollViewModel: CreatePollViewModel by viewModels() private val optionsAdapter: OptionsAdapter by lazy { OptionsAdapter( - answerCharLimit = arguments?.getInt(ARG_ANSWER_CHAR_LIMIT)?.takeIf { it > 0 }, + optionTextLimit = arguments?.getInt(ARG_OPTION_TEXT_LIMIT)?.takeIf { it > 0 }, onOptionChange = { id, text -> createPollViewModel.onOptionTextChanged(id, text) }, ) } @@ -85,7 +94,7 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { */ private fun setupDialog() { setupToolbar(binding.toolbar) - arguments?.getInt(ARG_TITLE_CHAR_LIMIT)?.takeIf { it > 0 }?.let { limit -> + arguments?.getInt(ARG_QUESTION_TEXT_LIMIT)?.takeIf { it > 0 }?.let { limit -> binding.question.filters = arrayOf(InputFilter.LengthFilter(limit)) } binding.multipleAnswersSwitch.setOnCheckedChangeListener { _, isChecked -> @@ -165,26 +174,27 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { public companion object { public const val TAG: String = "create_poll_dialog_fragment" - private const val ARG_TITLE_CHAR_LIMIT: String = "arg_title_char_limit" - private const val ARG_ANSWER_CHAR_LIMIT: String = "arg_answer_char_limit" + private const val ARG_QUESTION_TEXT_LIMIT: String = "arg_question_text_limit" + private const val ARG_OPTION_TEXT_LIMIT: String = "arg_option_text_limit" /** * Creates a new instance of [CreatePollDialogFragment]. * * @param createPollDialogListener The listener for poll creation events. - * @param titleCharLimit Optional character limit for the poll title. Null means no limit. - * @param answerCharLimit Optional character limit for poll answer options. Null means no limit. + * @param questionTextLimit Optional character limit for the poll question. Null means no limit. + * @param optionTextLimit Optional character limit for poll answer options. Null means no limit. * @return A new instance of [CreatePollDialogFragment]. */ + @JvmOverloads public fun newInstance( createPollDialogListener: CreatePollDialogListener, - titleCharLimit: Int? = null, - answerCharLimit: Int? = null, + questionTextLimit: Int? = null, + optionTextLimit: Int? = null, ): CreatePollDialogFragment { return CreatePollDialogFragment().apply { arguments = Bundle().apply { - titleCharLimit?.let { putInt(ARG_TITLE_CHAR_LIMIT, it) } - answerCharLimit?.let { putInt(ARG_ANSWER_CHAR_LIMIT, it) } + questionTextLimit?.let { putInt(ARG_QUESTION_TEXT_LIMIT, it) } + optionTextLimit?.let { putInt(ARG_OPTION_TEXT_LIMIT, it) } } }.setCreatePollDialogListener(createPollDialogListener) } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt index 04183426a7a..64b0325d3dc 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt @@ -28,10 +28,17 @@ import io.getstream.chat.android.ui.databinding.StreamUiPollOptionBinding import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater public class OptionsAdapter( - private val answerCharLimit: Int?, + private val optionTextLimit: Int?, private val onOptionChange: (id: Int, text: String) -> Unit, ) : ListAdapter(OptionDiffCallback) { + /** + * Builds an [OptionsAdapter] instance without providing option text limit. + * + * @param onOptionChange Callback invoked when the option text changes. + */ + public constructor(onOptionChange: (id: Int, text: String) -> Unit) : this(null, onOptionChange) + init { setHasStableIds(true) } @@ -41,7 +48,7 @@ public class OptionsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder = OptionViewHolder( parent = parent, - answerCharLimit = answerCharLimit, + optionTextLimit = optionTextLimit, onOptionChange = onOptionChange, ) @@ -56,14 +63,14 @@ public class OptionsAdapter( parent, false, ), - private val answerCharLimit: Int?, + private val optionTextLimit: Int?, private val onOptionChange: (id: Int, text: String) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { private lateinit var pollAnswer: PollAnswer init { - answerCharLimit?.let { limit -> + optionTextLimit?.let { limit -> binding.option.filters = arrayOf(InputFilter.LengthFilter(limit)) } }