-
Notifications
You must be signed in to change notification settings - Fork 319
Add configurable character limits and feature toggles for polls #6435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v6
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,38 +17,52 @@ | |
| 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 | ||
| import android.view.ViewGroup | ||
| import androidx.appcompat.app.AppCompatDialogFragment | ||
| import androidx.appcompat.widget.Toolbar | ||
| import androidx.core.content.ContextCompat | ||
| import androidx.core.os.BundleCompat | ||
| import androidx.core.view.WindowInsetsCompat | ||
| import androidx.core.view.isVisible | ||
| import androidx.core.widget.addTextChangedListener | ||
| import androidx.fragment.app.viewModels | ||
| import androidx.lifecycle.lifecycleScope | ||
| import io.getstream.chat.android.models.PollConfig | ||
| import io.getstream.chat.android.ui.ChatUI | ||
| import io.getstream.chat.android.ui.R | ||
| import io.getstream.chat.android.ui.common.utils.PollsConstants | ||
| import io.getstream.chat.android.ui.databinding.StreamUiFragmentCreatePollBinding | ||
| import io.getstream.chat.android.ui.utils.extensions.applyEdgeToEdgePadding | ||
| import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater | ||
| import kotlinx.coroutines.flow.collectLatest | ||
| import kotlinx.coroutines.launch | ||
| import kotlin.jvm.java | ||
|
|
||
| /** | ||
| * Represent the bottom sheet dialog that allows users to pick attachments. | ||
| * | ||
| * Use [newInstance] to create an instance with optional [PollsConfig]. | ||
| */ | ||
| public class CreatePollDialogFragment : AppCompatDialogFragment() { | ||
|
|
||
| private var _binding: StreamUiFragmentCreatePollBinding? = null | ||
| private val binding get() = _binding!! | ||
| private var createPollDialogListener: CreatePollDialogListener? = null | ||
| private val createPollViewModel: CreatePollViewModel by viewModels() | ||
| private val pollsConfig: PollsConfig by lazy { | ||
| arguments?.let { | ||
| BundleCompat.getParcelable(it, ARG_POLLS_CONFIG, PollsConfig::class.java) | ||
| } ?: ChatUI.pollsConfig | ||
| } | ||
| private val optionsAdapter: OptionsAdapter by lazy { | ||
| OptionsAdapter { id, text -> createPollViewModel.onOptionTextChanged(id, text) } | ||
| OptionsAdapter( | ||
| optionTextLimit = pollsConfig.optionTextLimit, | ||
| onOptionChange = { id, text -> createPollViewModel.onOptionTextChanged(id, text) }, | ||
| ) | ||
| } | ||
| private lateinit var sendMenuItem: MenuItem | ||
|
|
||
|
|
@@ -74,13 +88,60 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { | |
| super.onViewCreated(view, savedInstanceState) | ||
| binding.root.applyEdgeToEdgePadding(typeMask = WindowInsetsCompat.Type.systemBars()) | ||
| setupDialog() | ||
|
|
||
| if (savedInstanceState == null) { | ||
| // Configure poll feature visibility and default values based on pollsConfig | ||
| configurePollFeatures() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Configures the visibility and default values of poll features based on [pollsConfig]. | ||
| */ | ||
| private fun configurePollFeatures() { | ||
| // Configure multiple votes feature | ||
| createPollViewModel.setAllowMultipleVotes(pollsConfig.multipleVotes.defaultValue) | ||
| binding.multipleAnswersLabel.isVisible = pollsConfig.multipleVotes.configurable | ||
| binding.multipleAnswersSwitch.isVisible = pollsConfig.multipleVotes.configurable | ||
| if (pollsConfig.multipleVotes.configurable) { | ||
|
Comment on lines
+103
to
+106
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we're introducing a new edge case here. If we have
In Compose v7 we changed the behavior to allow Another option is to disallow the problematic combo, e.g. we could add a cc @andremion @VelikovPetar wdyt?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am more in favour of disallowing the problematic combination - I wouldn't want to change the default behaviour in a minor release. But once we port this addition to V7, I would say to align it with the compose implementation.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you be more explicit at what you'd like to see here? another option would be to remove the multiple votes configuration from this pr. I included it for completeness, but it is not a feature my team needs.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also leave it included for completeness. Basically the suggestion is to put this inside init {
require(multipleVotes.configurable || !multipleVotes.defaultValue) {
"Invalid PollsConfig: multipleVotes cannot have defaultValue=true while " +
"configurable=false as the user would be unable to set maxVotesAllowed."
}
} |
||
| binding.multipleAnswersSwitch.isChecked = pollsConfig.multipleVotes.defaultValue | ||
| binding.multipleAnswersCount.isVisible = pollsConfig.multipleVotes.defaultValue | ||
| } | ||
|
|
||
| // Configure anonymous poll feature | ||
| createPollViewModel.setAnnonymousPoll(pollsConfig.anonymousPoll.defaultValue) | ||
| binding.anonymousPollLabel.isVisible = pollsConfig.anonymousPoll.configurable | ||
| binding.anonymousPollSwitch.isVisible = pollsConfig.anonymousPoll.configurable | ||
| if (pollsConfig.anonymousPoll.configurable) { | ||
| binding.anonymousPollSwitch.isChecked = pollsConfig.anonymousPoll.defaultValue | ||
| } | ||
|
|
||
| // Configure suggest an option feature | ||
| createPollViewModel.setSuggestAnOption(pollsConfig.suggestAnOption.defaultValue) | ||
| binding.suggestAnOptionLabel.isVisible = pollsConfig.suggestAnOption.configurable | ||
| binding.suggestAnOptionSwitch.isVisible = pollsConfig.suggestAnOption.configurable | ||
| if (pollsConfig.suggestAnOption.configurable) { | ||
| binding.suggestAnOptionSwitch.isChecked = pollsConfig.suggestAnOption.defaultValue | ||
| } | ||
|
|
||
| // Configure add a comment feature | ||
| createPollViewModel.setAllowAnswers(pollsConfig.allowComments.defaultValue) | ||
| binding.addACommentLabel.isVisible = pollsConfig.allowComments.configurable | ||
| binding.addACommentLabelSwitch.isVisible = pollsConfig.allowComments.configurable | ||
| if (pollsConfig.allowComments.configurable) { | ||
| binding.addACommentLabelSwitch.isChecked = pollsConfig.allowComments.defaultValue | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Initializes the dialog. | ||
| */ | ||
| private fun setupDialog() { | ||
| setupToolbar(binding.toolbar) | ||
| pollsConfig.questionTextLimit?.takeIf { it > 0 }?.let { limit -> | ||
| binding.question.filters = arrayOf(InputFilter.LengthFilter(limit)) | ||
| } | ||
|
|
||
| binding.multipleAnswersSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| binding.multipleAnswersCount.isVisible = isChecked | ||
| createPollViewModel.setAllowMultipleVotes(isChecked) | ||
|
|
@@ -97,6 +158,9 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { | |
| binding.suggestAnOptionSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| createPollViewModel.setSuggestAnOption(isChecked) | ||
| } | ||
| binding.addACommentLabelSwitch.setOnCheckedChangeListener { _, isChecked -> | ||
| createPollViewModel.setAllowAnswers(isChecked) | ||
| } | ||
|
Comment on lines
+161
to
+163
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this, but it is more or less unrelated to this pr. I think the current functionality(before this pr) is that this switch doesn't do anything. I wasn't able to get this to work in my testing with the changes in this pr either though, so I'm curious what your thoughts are.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It definitely looks like we had a bug both on the creation side and the consuming side, since we never read "allow answers" when displaying poll messages. We'll check that on our side, thanks for fixing the problem at creation time! |
||
| binding.addOption.setOnClickListener { | ||
| createPollViewModel.createOption() | ||
| } | ||
|
|
@@ -158,15 +222,25 @@ public class CreatePollDialogFragment : AppCompatDialogFragment() { | |
|
|
||
| public companion object { | ||
| public const val TAG: String = "create_poll_dialog_fragment" | ||
| private const val ARG_POLLS_CONFIG: String = "arg_polls_config" | ||
|
|
||
| /** | ||
| * Creates a new instance of [CreatePollDialogFragment]. | ||
| * | ||
| * @param createPollDialogListener The listener for poll creation events. | ||
| * @param pollsConfig Optional configuration for poll features. Defaults to [ChatUI.pollsConfig]. | ||
| * @return A new instance of [CreatePollDialogFragment]. | ||
| */ | ||
| public fun newInstance(createPollDialogListener: CreatePollDialogListener): CreatePollDialogFragment { | ||
| return CreatePollDialogFragment() | ||
| .setCreatePollDialogListener(createPollDialogListener) | ||
| @JvmOverloads | ||
| public fun newInstance( | ||
| createPollDialogListener: CreatePollDialogListener, | ||
| pollsConfig: PollsConfig? = null, | ||
| ): CreatePollDialogFragment { | ||
| return CreatePollDialogFragment().apply { | ||
| arguments = Bundle().apply { | ||
| pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config) } | ||
| } | ||
| }.setCreatePollDialogListener(createPollDialogListener) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package io.getstream.chat.android.ui.feature.messages.composer.attachment.picker.poll | ||
|
|
||
| import android.os.Parcelable | ||
| import kotlinx.parcelize.Parcelize | ||
|
|
||
| /** | ||
| * Configuration for individual poll entry feature. | ||
| * | ||
| * @param configurable Indicates whether the poll entry is configurable. When false, the UI element is hidden. | ||
| * @param defaultValue Indicates the default value of the poll entry. | ||
| */ | ||
| @Parcelize | ||
| public data class PollFeatureConfig( | ||
| val configurable: Boolean, | ||
| val defaultValue: Boolean, | ||
| ) : Parcelable { | ||
| public companion object { | ||
| /** | ||
| * The default configuration for a poll entry. It will make it configurable and disabled by default. | ||
| */ | ||
| public val Default: PollFeatureConfig = PollFeatureConfig( | ||
| configurable = true, | ||
| defaultValue = false, | ||
| ) | ||
|
|
||
| /** | ||
| * The feature should not be supported, so it is not configurable by the user and hidden from the UI. | ||
| */ | ||
| public val NotConfigurable: PollFeatureConfig = PollFeatureConfig( | ||
| configurable = false, | ||
| defaultValue = false, | ||
| ) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| * 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.composer.attachment.picker.poll | ||
|
|
||
| import android.os.Parcelable | ||
| import kotlinx.parcelize.Parcelize | ||
|
|
||
| /** | ||
| * The configuration for the various poll features. It determines if the user can or cannot enable certain poll features. | ||
| * | ||
| * @param multipleVotes Configuration for allowing multiple votes in a poll. | ||
| * @param anonymousPoll Configuration for enabling anonymous polls. | ||
| * @param suggestAnOption Configuration for allowing users to suggest options in a poll. | ||
| * @param allowComments Configuration for adding comments to a poll. | ||
| * @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. | ||
| */ | ||
| @Parcelize | ||
| public data class PollsConfig( | ||
|
gpunto marked this conversation as resolved.
|
||
| val multipleVotes: PollFeatureConfig = PollFeatureConfig.Default, | ||
| val anonymousPoll: PollFeatureConfig = PollFeatureConfig.Default, | ||
| val suggestAnOption: PollFeatureConfig = PollFeatureConfig.Default, | ||
| val allowComments: PollFeatureConfig = PollFeatureConfig.Default, | ||
| val questionTextLimit: Int? = null, | ||
| val optionTextLimit: Int? = null, | ||
| ) : Parcelable { | ||
| public companion object { | ||
| /** | ||
| * The default configuration for polls. All features are configurable and disabled by default. | ||
| */ | ||
| public val Default: PollsConfig = PollsConfig() | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.