Undisclosed poll implementation.

This commit is contained in:
Onuray Sahin 2022-01-24 14:31:50 +03:00
parent 7f97e78ba3
commit b0b92c062e
20 changed files with 247 additions and 29 deletions

View File

@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PollCreationInfo(
@Json(name = "question") val question: PollQuestion? = null,
@Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed",
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED,
@Json(name = "max_selections") val maxSelections: Int = 1,
@Json(name = "answers") val answers: List<PollAnswer>? = null
)

View File

@ -0,0 +1,35 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class PollType {
/**
* Voters should see results as soon as they have voted.
*/
@Json(name = "org.matrix.msc3381.poll.disclosed")
DISCLOSED,
/**
* Results should be only revealed when the poll is ended.
*/
@Json(name = "org.matrix.msc3381.poll.undisclosed")
UNDISCLOSED
}

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.relation
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
@ -66,11 +67,13 @@ interface RelationService {
/**
* Edit a poll.
* @param pollType indicates open or closed polls
* @param targetEvent The poll event to edit
* @param question The edited question
* @param options The edited options
*/
fun editPoll(targetEvent: TimelineEvent,
pollType: PollType,
question: String,
options: List<String>): Cancelable

View File

@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
@ -91,11 +92,12 @@ interface SendService {
/**
* Send a poll to the room.
* @param pollType indicates open or closed polls
* @param question the question
* @param options list of options
* @return a [Cancelable]
*/
fun sendPoll(question: String, options: List<String>): Cancelable
fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable
/**
* Method to send a poll response.

View File

@ -24,6 +24,7 @@ import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
@ -113,9 +114,10 @@ internal class DefaultRelationService @AssistedInject constructor(
}
override fun editPoll(targetEvent: TimelineEvent,
pollType: PollType,
question: String,
options: List<String>): Cancelable {
return eventEditor.editPoll(targetEvent, question, options)
return eventEditor.editPoll(targetEvent, pollType, question, options)
}
override fun editTextMessage(targetEvent: TimelineEvent,

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.relation
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
@ -59,17 +60,18 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
}
fun editPoll(targetEvent: TimelineEvent,
pollType: PollType,
question: String,
options: List<String>): Cancelable {
val roomId = targetEvent.roomId
if (targetEvent.root.sendState.hasFailed()) {
val editedEvent = eventFactory.createPollEvent(roomId, question, options).copy(
val editedEvent = eventFactory.createPollEvent(roomId, pollType, question, options).copy(
eventId = targetEvent.eventId
)
return sendFailedEvent(targetEvent, editedEvent)
} else if (targetEvent.root.sendState.isSent()) {
val event = eventFactory
.createPollReplaceEvent(roomId, targetEvent.eventId, question, options)
.createPollReplaceEvent(roomId, pollType, targetEvent.eventId, question, options)
return sendReplaceEvent(roomId, event)
} else {
Timber.w("Can't edit a sending event")

View File

@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.session.room.send.SendState
@ -103,8 +104,8 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) }
}
override fun sendPoll(question: String, options: List<String>): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, question, options)
override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, pollType, question, options)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}

View File

@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
import org.matrix.android.sdk.api.session.room.model.message.PollResponse
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
@ -126,12 +127,14 @@ internal class LocalEchoEventFactory @Inject constructor(
}
private fun createPollContent(question: String,
options: List<String>): MessagePollContent {
options: List<String>,
pollType: PollType): MessagePollContent {
return MessagePollContent(
pollCreationInfo = PollCreationInfo(
question = PollQuestion(
question = question
),
kind = pollType,
answers = options.map { option ->
PollAnswer(
id = UUID.randomUUID().toString(),
@ -143,12 +146,13 @@ internal class LocalEchoEventFactory @Inject constructor(
}
fun createPollReplaceEvent(roomId: String,
pollType: PollType,
targetEventId: String,
question: String,
options: List<String>): Event {
val newContent = MessagePollContent(
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
newContent = createPollContent(question, options).toContent()
newContent = createPollContent(question, options, pollType).toContent()
)
val localId = LocalEcho.createLocalEchoId()
return Event(
@ -186,9 +190,10 @@ internal class LocalEchoEventFactory @Inject constructor(
}
fun createPollEvent(roomId: String,
pollType: PollType,
question: String,
options: List<String>): Event {
val content = createPollContent(question, options)
val content = createPollContent(question, options, pollType)
val localId = LocalEcho.createLocalEchoId()
return Event(
roomId = roomId,

View File

@ -89,6 +89,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.message.getFileName
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
@ -186,11 +187,14 @@ class MessageItemFactory @Inject constructor(
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
val isPollSent = informationData.sendState.isSent()
val isPollUndisclosed = pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
when {
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
didUserVoted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
else -> if (it == 0) {
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
isPollUndisclosed -> ""
didUserVoted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
else -> if (it == 0) {
stringProvider.getString(R.string.poll_no_votes_cast)
} else {
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
@ -214,6 +218,9 @@ class MessageItemFactory @Inject constructor(
// Poll is ended. Disable option, show votes and mark the winner.
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
} else if (isPollUndisclosed) {
// Poll is closed. Enable option, hide votes and mark the user's selection.
PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
} else if (didUserVoted) {
// User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)

View File

@ -22,9 +22,9 @@ import androidx.core.view.children
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
import im.vector.app.features.home.room.detail.RoomDetailAction
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class PollItem : AbsMessageItem<PollItem.Holder>() {

View File

@ -23,6 +23,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.setAttributeTintedImageResource
import im.vector.app.databinding.ItemPollOptionBinding
@ -43,11 +44,12 @@ class PollOptionView @JvmOverloads constructor(
views.optionNameTextView.text = state.optionAnswer
when (state) {
is PollOptionViewState.PollSending -> renderPollSending()
is PollOptionViewState.PollEnded -> renderPollEnded(state)
is PollOptionViewState.PollReady -> renderPollReady()
is PollOptionViewState.PollVoted -> renderPollVoted(state)
}
is PollOptionViewState.PollSending -> renderPollSending()
is PollOptionViewState.PollEnded -> renderPollEnded(state)
is PollOptionViewState.PollReady -> renderPollReady()
is PollOptionViewState.PollVoted -> renderPollVoted(state)
is PollOptionViewState.PollUndisclosed -> renderPollUndisclosed(state)
}.exhaustive
}
private fun renderPollSending() {
@ -78,6 +80,12 @@ class PollOptionView @JvmOverloads constructor(
renderVoteSelection(state.isSelected)
}
private fun renderPollUndisclosed(state: PollOptionViewState.PollUndisclosed) {
views.optionCheckImageView.isVisible = true
views.optionWinnerImageView.isVisible = false
renderVoteSelection(state.isSelected)
}
private fun showVotes(voteCount: Int, votePercentage: Double) {
views.optionVoteCountTextView.apply {
isVisible = true

View File

@ -51,4 +51,12 @@ sealed class PollOptionViewState(open val optionId: String,
val votePercentage: Double,
val isWinner: Boolean
) : PollOptionViewState(optionId, optionAnswer)
/**
* Represent a poll that is closed, votes will be hidden until the poll is ended.
*/
data class PollUndisclosed(override val optionId: String,
override val optionAnswer: String,
val isSelected: Boolean
) : PollOptionViewState(optionId, optionAnswer)
}

View File

@ -17,11 +17,13 @@
package im.vector.app.features.poll.create
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.message.PollType
sealed class CreatePollAction : VectorViewModelAction {
data class OnQuestionChanged(val question: String) : CreatePollAction()
data class OnOptionChanged(val index: Int, val option: String) : CreatePollAction()
data class OnDeleteOption(val index: Int) : CreatePollAction()
data class OnPollTypeChanged(val pollType: PollType) : CreatePollAction()
object OnAddOption : CreatePollAction()
object OnCreatePoll : CreatePollAction()
}

View File

@ -28,6 +28,7 @@ import im.vector.app.core.ui.list.genericItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formEditTextWithDeleteItem
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.session.room.model.message.PollType
import javax.inject.Inject
class CreatePollController @Inject constructor(
@ -47,6 +48,26 @@ class CreatePollController @Inject constructor(
val currentState = state ?: return
val host = this
genericItem {
id("poll_type_title")
style(ItemStyle.BIG_TEXT)
title(host.stringProvider.getString(R.string.poll_type_title).toEpoxyCharSequence())
}
pollTypeSelectionItem {
id("poll_type_selection")
pollType(currentState.pollType)
pollTypeChangedListener { _, id ->
host.callback?.onPollTypeChanged(
if (id == R.id.openPollTypeRadioButton) {
PollType.DISCLOSED
} else {
PollType.UNDISCLOSED
}
)
}
}
genericItem {
id("question_title")
style(ItemStyle.BIG_TEXT)
@ -110,5 +131,6 @@ class CreatePollController @Inject constructor(
fun onOptionChanged(index: Int, option: String)
fun onDeleteOption(index: Int)
fun onAddOption()
fun onPollTypeChanged(type: PollType)
}
}

View File

@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentCreatePollBinding
import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.message.PollType
import javax.inject.Inject
@Parcelize
@ -60,18 +61,18 @@ class CreatePollFragment @Inject constructor(
when (args.mode) {
PollMode.CREATE -> {
views.createPollTitle.text = getString(R.string.create_poll_title)
views.createPollToolbar.title = getString(R.string.create_poll_title)
views.createPollButton.text = getString(R.string.create_poll_title)
}
PollMode.EDIT -> {
views.createPollTitle.text = getString(R.string.edit_poll_title)
views.createPollToolbar.title = getString(R.string.edit_poll_title)
views.createPollButton.text = getString(R.string.edit_poll_title)
}
}.exhaustive
views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true)
// workaround for https://github.com/vector-im/element-android/issues/4735
views.createPollRecyclerView.setItemViewCacheSize(MAX_OPTIONS_COUNT + 4)
views.createPollRecyclerView.setItemViewCacheSize(MAX_OPTIONS_COUNT + 6)
controller.callback = this
views.createPollButton.debouncedClicks {
@ -117,6 +118,10 @@ class CreatePollFragment @Inject constructor(
}
}
override fun onPollTypeChanged(type: PollType) {
viewModel.handle(CreatePollAction.OnPollTypeChanged(type))
}
private fun handleSuccess() {
requireActivity().finish()
}

View File

@ -25,6 +25,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
class CreatePollViewModel @AssistedInject constructor(
@ -70,13 +71,15 @@ class CreatePollViewModel @AssistedInject constructor(
val event = room.getTimeLineEvent(eventId) ?: return
val content = event.getLastMessageContent() as? MessagePollContent ?: return
val pollType = content.pollCreationInfo?.kind ?: PollType.DISCLOSED
val question = content.pollCreationInfo?.question?.question ?: ""
val options = content.pollCreationInfo?.answers?.mapNotNull { it.answer } ?: List(MIN_OPTIONS_COUNT) { "" }
setState {
copy(
question = question,
options = options
options = options,
pollType = pollType
)
}
}
@ -88,6 +91,7 @@ class CreatePollViewModel @AssistedInject constructor(
is CreatePollAction.OnDeleteOption -> handleOnDeleteOption(action.index)
is CreatePollAction.OnOptionChanged -> handleOnOptionChanged(action.index, action.option)
is CreatePollAction.OnQuestionChanged -> handleOnQuestionChanged(action.question)
is CreatePollAction.OnPollTypeChanged -> handleOnPollTypeChanged(action.pollType)
}
}
@ -102,17 +106,17 @@ class CreatePollViewModel @AssistedInject constructor(
}
else -> {
when (state.mode) {
PollMode.CREATE -> room.sendPoll(state.question, nonEmptyOptions)
PollMode.EDIT -> sendEditedPoll(state.editedEventId!!, state.question, nonEmptyOptions)
PollMode.CREATE -> room.sendPoll(state.pollType, state.question, nonEmptyOptions)
PollMode.EDIT -> sendEditedPoll(state.editedEventId!!, state.pollType, state.question, nonEmptyOptions)
}
_viewEvents.post(CreatePollViewEvents.Success)
}
}
}
private fun sendEditedPoll(editedEventId: String, question: String, options: List<String>) {
private fun sendEditedPoll(editedEventId: String, pollType: PollType, question: String, options: List<String>) {
val editedEvent = room.getTimeLineEvent(editedEventId) ?: return
room.editPoll(editedEvent, question, options)
room.editPoll(editedEvent, pollType, question, options)
}
private fun handleOnAddOption() {
@ -150,6 +154,14 @@ class CreatePollViewModel @AssistedInject constructor(
}
}
private fun handleOnPollTypeChanged(pollType: PollType) {
setState {
copy(
pollType = pollType
)
}
}
private fun canCreatePoll(question: String, options: List<String>): Boolean {
return question.isNotEmpty() &&
options.filter { it.isNotEmpty() }.size >= MIN_OPTIONS_COUNT

View File

@ -17,6 +17,7 @@
package im.vector.app.features.poll.create
import com.airbnb.mvrx.MavericksState
import org.matrix.android.sdk.api.session.room.model.message.PollType
data class CreatePollViewState(
val roomId: String,
@ -25,7 +26,8 @@ data class CreatePollViewState(
val question: String = "",
val options: List<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
val canCreatePoll: Boolean = false,
val canAddMoreOptions: Boolean = true
val canAddMoreOptions: Boolean = true,
val pollType: PollType = PollType.DISCLOSED
) : MavericksState {
constructor(args: CreatePollArgs) : this(

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 im.vector.app.features.poll.create
import android.widget.RadioGroup
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import org.matrix.android.sdk.api.session.room.model.message.PollType
@EpoxyModelClass(layout = R.layout.item_poll_type_selection)
abstract class PollTypeSelectionItem : VectorEpoxyModel<PollTypeSelectionItem.Holder>() {
@EpoxyAttribute
var pollType: PollType = PollType.DISCLOSED
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var pollTypeChangedListener: RadioGroup.OnCheckedChangeListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.pollTypeRadioGroup.check(
when (pollType) {
PollType.DISCLOSED -> R.id.openPollTypeRadioButton
PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton
}
)
holder.pollTypeRadioGroup.setOnCheckedChangeListener(pollTypeChangedListener)
}
override fun unbind(holder: Holder) {
super.unbind(holder)
holder.pollTypeRadioGroup.setOnCheckedChangeListener(null)
}
class Holder : VectorEpoxyHolder() {
val pollTypeRadioGroup by bind<RadioGroup>(R.id.pollTypeRadioGroup)
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pollTypeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:orientation="vertical"
android:paddingBottom="@dimen/layout_vertical_margin">
<RadioButton
android:id="@+id/openPollTypeRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:text="@string/open_poll_option_title" />
<TextView
style="@style/TextAppearance.Vector.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/open_poll_option_description" />
<RadioButton
android:id="@+id/closedPollTypeRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:minHeight="0dp"
android:text="@string/closed_poll_option_title" />
<TextView
style="@style/TextAppearance.Vector.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/closed_poll_option_description" />
</RadioGroup>

View File

@ -3708,12 +3708,17 @@
<string name="end_poll_confirmation_description">This will stop people from being able to vote and will display the final results of the poll.</string>
<string name="end_poll_confirmation_approve_button">End poll</string>
<string name="labs_enable_polls">Enable Polls</string>
<string name="poll_response_room_list_preview">Vote casted</string>
<string name="poll_response_room_list_preview">Vote cast</string>
<string name="poll_end_room_list_preview">Poll ended</string>
<string name="delete_poll_dialog_title">Remove poll</string>
<string name="delete_poll_dialog_content">Are you sure you want to remove this poll? You won\'t be able to recover it once removed.</string>
<string name="edit_poll_title">Edit poll</string>
<string name="edit_poll_button">EDIT POLL</string>
<string name="poll_type_title">Poll type</string>
<string name="open_poll_option_title">Open poll</string>
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
<string name="closed_poll_option_title">Closed poll</string>
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
<string name="tooltip_attachment_photo">Open camera</string>
<string name="tooltip_attachment_gallery">Send images and videos</string>