mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Implement poll in timeline ui.
This commit is contained in:
parent
2a3a55894f
commit
06485cf5e4
25 changed files with 521 additions and 62 deletions
22
vector/sampledata/poll.json
Normal file
22
vector/sampledata/poll.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"question": "What type of food should we have at the party?",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"answer": "Italian \uD83C\uDDEE\uD83C\uDDF9",
|
||||||
|
"votes": "9 votes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer": "Chinese \uD83C\uDDE8\uD83C\uDDF3",
|
||||||
|
"votes": "1 vote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer": "Brazilian \uD83C\uDDE7\uD83C\uDDF7",
|
||||||
|
"votes": "0 votes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer": "French \uD83C\uDDEB\uD83C\uDDF7",
|
||||||
|
"votes": "15 votes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalVotes": "Based on 20 votes"
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
|
||||||
data class CancelSend(val eventId: String, val force: Boolean) : RoomDetailAction()
|
data class CancelSend(val eventId: String, val force: Boolean) : RoomDetailAction()
|
||||||
|
|
||||||
data class ReplyToOptions(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()
|
data class RegisterVoteToPoll(val eventId: String, val optionKey: String) : RoomDetailAction()
|
||||||
|
|
||||||
data class ReportContent(
|
data class ReportContent(
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
|
@ -116,4 +116,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction()
|
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction()
|
||||||
object PlayOrPauseRecordingPlayback : RoomDetailAction()
|
object PlayOrPauseRecordingPlayback : RoomDetailAction()
|
||||||
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction()
|
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction()
|
||||||
|
|
||||||
|
// Poll
|
||||||
|
data class EndPoll(val eventId: String) : RoomDetailAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2017,6 +2017,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
startActivity(KeysBackupRestoreActivity.intent(it))
|
startActivity(KeysBackupRestoreActivity.intent(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is EventSharedAction.EndPoll -> {
|
||||||
|
roomDetailViewModel.handle(RoomDetailAction.EndPoll(action.eventId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -309,7 +309,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
is RoomDetailAction.RegisterVoteToPoll -> handleRegisterVoteToPoll(action)
|
||||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
|
@ -355,6 +355,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
_viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
|
_viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
|
||||||
}
|
}
|
||||||
|
is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -983,10 +984,14 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) {
|
private fun handleRegisterVoteToPoll(action: RoomDetailAction.RegisterVoteToPoll) {
|
||||||
// Do not allow to reply to unsent local echo
|
// Do not allow to reply to unsent local echo
|
||||||
if (LocalEcho.isLocalEchoId(action.eventId)) return
|
if (LocalEcho.isLocalEchoId(action.eventId)) return
|
||||||
room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue)
|
room.registerVoteToPoll(action.eventId, action.optionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleEndPoll(eventId: String) {
|
||||||
|
room.endPoll(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeSyncState() {
|
private fun observeSyncState() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.text.style.AbsoluteSizeSpan
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
@ -48,12 +49,12 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageOptionsItem_
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessagePollItem_
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollItem
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
|
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
||||||
|
@ -80,14 +81,11 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
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.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
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.MessageVideoContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getFileName
|
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.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||||
|
@ -125,7 +123,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
pillsPostProcessorFactory.create(roomId)
|
pillsPostProcessorFactory.create(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
|
fun create(params: TimelineItemFactoryParams): EpoxyModel<*>? {
|
||||||
val event = params.event
|
val event = params.event
|
||||||
val highlight = params.isHighlighted
|
val highlight = params.isHighlighted
|
||||||
val callback = params.callback
|
val callback = params.callback
|
||||||
|
@ -168,41 +166,24 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessagePollResponseContent -> noticeItemFactory.create(params)
|
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildOptionsMessageItem(messageContent: MessageOptionsContent,
|
private fun buildPollContent(messageContent: MessagePollContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
attributes: AbsMessageItem.Attributes): PollItem? {
|
||||||
return when (messageContent.optionType) {
|
return PollItem_()
|
||||||
OPTION_TYPE_POLL -> {
|
.attributes(attributes)
|
||||||
MessagePollItem_()
|
.eventId(informationData.eventId)
|
||||||
.attributes(attributes)
|
.pollResponseSummary(informationData.pollResponseAggregatedSummary)
|
||||||
.callback(callback)
|
.pollContent(messageContent)
|
||||||
.informationData(informationData)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.optionsContent(messageContent)
|
.callback(callback)
|
||||||
.highlighted(highlight)
|
|
||||||
}
|
|
||||||
OPTION_TYPE_BUTTONS -> {
|
|
||||||
MessageOptionsItem_()
|
|
||||||
.attributes(attributes)
|
|
||||||
.callback(callback)
|
|
||||||
.informationData(informationData)
|
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
|
||||||
.optionsContent(messageContent)
|
|
||||||
.highlighted(highlight)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Not supported optionType
|
|
||||||
buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
||||||
|
|
|
@ -48,6 +48,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
when (event.root.getClearType()) {
|
when (event.root.getClearType()) {
|
||||||
// Message itemsX
|
// Message itemsX
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
|
EventType.POLL_START,
|
||||||
EventType.MESSAGE -> messageItemFactory.create(params)
|
EventType.MESSAGE -> messageItemFactory.create(params)
|
||||||
EventType.STATE_ROOM_TOMBSTONE,
|
EventType.STATE_ROOM_TOMBSTONE,
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
|
|
|
@ -107,9 +107,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
|
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
|
||||||
PollResponseData(
|
PollResponseData(
|
||||||
myVote = it.aggregatedContent?.myVote,
|
myVote = it.aggregatedContent?.myVote,
|
||||||
isClosed = it.closedTime ?: Long.MAX_VALUE > System.currentTimeMillis(),
|
isClosed = it.closedTime != null,
|
||||||
votes = it.aggregatedContent?.votes
|
votes = it.aggregatedContent?.votes
|
||||||
?.groupBy({ it.optionIndex }, { it.userId })
|
?.groupBy({ it.option }, { it.userId })
|
||||||
?.mapValues { it.value.size }
|
?.mapValues { it.value.size }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,7 +50,8 @@ object TimelineDisplayableEvents {
|
||||||
EventType.STATE_ROOM_TOMBSTONE,
|
EventType.STATE_ROOM_TOMBSTONE,
|
||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
|
EventType.POLL_START
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ data class ReadReceiptData(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PollResponseData(
|
data class PollResponseData(
|
||||||
val myVote: Int?,
|
val myVote: String?,
|
||||||
val votes: Map<Int, Int>?,
|
val votes: Map<String, Int>?,
|
||||||
val isClosed: Boolean = false
|
val isClosed: Boolean = false
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 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.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
|
abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var pollContent: MessagePollContent? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var pollResponseSummary: PollResponseData? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var eventId: String? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
val relatedEventId = eventId ?: return
|
||||||
|
|
||||||
|
renderSendState(holder.view, holder.questionTextView)
|
||||||
|
|
||||||
|
holder.questionTextView.text = pollContent?.pollCreationInfo?.question?.question
|
||||||
|
|
||||||
|
holder.optionsContainer.removeAllViews()
|
||||||
|
|
||||||
|
val isEnded = pollResponseSummary?.isClosed.orFalse()
|
||||||
|
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
||||||
|
val showVotes = didUserVoted || isEnded
|
||||||
|
val totalVotes = pollResponseSummary?.votes?.map { it.value }?.sum() ?: 0
|
||||||
|
val winnerVoteCount = pollResponseSummary?.votes?.map { it.value }?.maxOrNull() ?: 0
|
||||||
|
|
||||||
|
pollContent?.pollCreationInfo?.answers?.forEach { option ->
|
||||||
|
val isMyVote = pollResponseSummary?.myVote?.let { option.id == it }.orFalse()
|
||||||
|
val voteCount = pollResponseSummary?.votes?.get(option.id) ?: 0
|
||||||
|
val votePercentage = if (voteCount == 0 && totalVotes == 0) 0.0 else voteCount.toDouble() / totalVotes
|
||||||
|
|
||||||
|
holder.optionsContainer.addView(
|
||||||
|
PollOptionItem(holder.view.context).apply {
|
||||||
|
update(optionName = option.answer ?: "",
|
||||||
|
isSelected = isMyVote,
|
||||||
|
isWinner = voteCount == winnerVoteCount,
|
||||||
|
isEnded = isEnded,
|
||||||
|
showVote = showVotes,
|
||||||
|
voteCount = voteCount,
|
||||||
|
votePercentage = votePercentage,
|
||||||
|
callback = object : PollOptionItem.Callback {
|
||||||
|
override fun onOptionClicked() {
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.RegisterVoteToPoll(relatedEventId, option.id ?: ""))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.totalVotesTextView.apply {
|
||||||
|
text = when {
|
||||||
|
isEnded -> resources.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes)
|
||||||
|
didUserVoted -> resources.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes)
|
||||||
|
else -> resources.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
|
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||||
|
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
||||||
|
val totalVotesTextView by bind<TextView>(R.id.optionsTotalVotesTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val STUB_ID = R.id.messageContentPollStub
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.AttributeSet
|
||||||
|
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.setAttributeTintedImageResource
|
||||||
|
import im.vector.app.databinding.ItemPollOptionBinding
|
||||||
|
|
||||||
|
class PollOptionItem @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onOptionClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var views: ItemPollOptionBinding
|
||||||
|
private var callback: Callback? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViews() {
|
||||||
|
inflate(context, R.layout.item_poll_option, this)
|
||||||
|
views = ItemPollOptionBinding.bind(this)
|
||||||
|
|
||||||
|
views.root.setOnClickListener { callback?.onOptionClicked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(optionName: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
isWinner: Boolean,
|
||||||
|
isEnded: Boolean,
|
||||||
|
showVote: Boolean,
|
||||||
|
voteCount: Int,
|
||||||
|
votePercentage: Double,
|
||||||
|
callback: Callback) {
|
||||||
|
this.callback = callback
|
||||||
|
views.optionNameTextView.text = optionName
|
||||||
|
|
||||||
|
views.optionCheckImageView.isVisible = !isEnded
|
||||||
|
|
||||||
|
if (isEnded && isWinner) {
|
||||||
|
views.optionBorderImageView.setAttributeTintedImageResource(R.drawable.bg_poll_option, R.attr.colorPrimary)
|
||||||
|
views.optionVoteProgress.progressDrawable = AppCompatResources.getDrawable(context, R.drawable.poll_option_progressbar_checked)
|
||||||
|
views.optionWinnerImageView.isVisible = true
|
||||||
|
} else if (isSelected) {
|
||||||
|
views.optionBorderImageView.setAttributeTintedImageResource(R.drawable.bg_poll_option, R.attr.colorPrimary)
|
||||||
|
views.optionVoteProgress.progressDrawable = AppCompatResources.getDrawable(context, R.drawable.poll_option_progressbar_checked)
|
||||||
|
views.optionCheckImageView.setImageResource(R.drawable.poll_option_checked)
|
||||||
|
views.optionWinnerImageView.isVisible = false
|
||||||
|
} else {
|
||||||
|
views.optionBorderImageView.setAttributeTintedImageResource(R.drawable.bg_poll_option, R.attr.vctr_content_quinary)
|
||||||
|
views.optionVoteProgress.progressDrawable = AppCompatResources.getDrawable(context, R.drawable.poll_option_progressbar_unchecked)
|
||||||
|
views.optionCheckImageView.setImageResource(R.drawable.poll_option_unchecked)
|
||||||
|
views.optionWinnerImageView.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showVote) {
|
||||||
|
views.optionVoteCountTextView.apply {
|
||||||
|
isVisible = true
|
||||||
|
text = resources.getQuantityString(R.plurals.poll_option_vote_count, voteCount, voteCount)
|
||||||
|
}
|
||||||
|
views.optionVoteProgress.apply {
|
||||||
|
val progressValue = (votePercentage * 100).toInt()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
setProgress(progressValue, true)
|
||||||
|
} else {
|
||||||
|
progress = progressValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
views.optionVoteCountTextView.isVisible = false
|
||||||
|
views.optionVoteProgress.progress = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
vector/src/main/res/drawable/bg_poll_option.xml
Normal file
9
vector/src/main/res/drawable/bg_poll_option.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="?vctr_content_quinary" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
8
vector/src/main/res/drawable/divider_poll_options.xml
Normal file
8
vector/src/main/res/drawable/divider_poll_options.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<size
|
||||||
|
android:width="1dp"
|
||||||
|
android:height="16dp" />
|
||||||
|
|
||||||
|
</shape>
|
13
vector/src/main/res/drawable/ic_check_on_white.xml
Normal file
13
vector/src/main/res/drawable/ic_check_on_white.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M20,7L9,18L4,13"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
9
vector/src/main/res/drawable/ic_poll_winner.xml
Normal file
9
vector/src/main/res/drawable/ic_poll_winner.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M12.6667,3.3333H11.3333V2.6667C11.3333,2.3 11.0333,2 10.6667,2H5.3333C4.9667,2 4.6667,2.3 4.6667,2.6667V3.3333H3.3333C2.6,3.3333 2,3.9333 2,4.6667V5.3333C2,7.0333 3.28,8.42 4.9267,8.6267C5.3467,9.6267 6.2467,10.38 7.3333,10.6V12.6667H5.3333C4.9667,12.6667 4.6667,12.9667 4.6667,13.3333C4.6667,13.7 4.9667,14 5.3333,14H10.6667C11.0333,14 11.3333,13.7 11.3333,13.3333C11.3333,12.9667 11.0333,12.6667 10.6667,12.6667H8.6667V10.6C9.7533,10.38 10.6533,9.6267 11.0733,8.6267C12.72,8.42 14,7.0333 14,5.3333V4.6667C14,3.9333 13.4,3.3333 12.6667,3.3333ZM3.3333,5.3333V4.6667H4.6667V7.2133C3.8933,6.9333 3.3333,6.2 3.3333,5.3333ZM12.6667,5.3333C12.6667,6.2 12.1067,6.9333 11.3333,7.2133V4.6667H12.6667V5.3333Z"
|
||||||
|
android:fillColor="#0DBD8B"/>
|
||||||
|
</vector>
|
14
vector/src/main/res/drawable/poll_option_checked.xml
Normal file
14
vector/src/main/res/drawable/poll_option_checked.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="?colorPrimary" />
|
||||||
|
<size
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item
|
||||||
|
android:drawable="@drawable/ic_check_on_white"
|
||||||
|
android:gravity="center" />
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="?vctr_system" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item android:id="@android:id/progress">
|
||||||
|
<clip>
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="?colorPrimary" />
|
||||||
|
</shape>
|
||||||
|
</clip>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="?vctr_system" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item android:id="@android:id/progress">
|
||||||
|
<clip>
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="?vctr_content_quaternary" />
|
||||||
|
</shape>
|
||||||
|
</clip>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
13
vector/src/main/res/drawable/poll_option_unchecked.xml
Normal file
13
vector/src/main/res/drawable/poll_option_unchecked.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="?vctr_disabled_view_color" />
|
||||||
|
<size
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp" />
|
||||||
|
|
||||||
|
</shape>
|
81
vector/src/main/res/layout/item_poll_option.xml
Normal file
81
vector/src/main/res/layout/item_poll_option.xml
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/optionContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/optionBorderImageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="64dp"
|
||||||
|
android:src="@drawable/bg_poll_option"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/optionCheckImageView"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/poll_option_unchecked"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/optionNameTextView"
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/optionWinnerImageView"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/optionCheckImageView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@sample/poll.json/data/answer" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/optionWinnerImageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:contentDescription="@string/a11y_poll_winner_option"
|
||||||
|
android:src="@drawable/ic_poll_winner"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/optionVoteCountTextView"
|
||||||
|
style="@style/Widget.Vector.TextView.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/optionVoteProgress"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/optionVoteProgress"
|
||||||
|
tools:text="@sample/poll.json/data/votes"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/optionVoteProgress"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="6dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:progressDrawable="@drawable/poll_option_progressbar_checked"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/optionNameTextView"
|
||||||
|
tools:progress="60" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -118,20 +118,6 @@
|
||||||
android:layout_marginEnd="56dp"
|
android:layout_marginEnd="56dp"
|
||||||
android:layout="@layout/item_timeline_event_redacted_stub" />
|
android:layout="@layout/item_timeline_event_redacted_stub" />
|
||||||
|
|
||||||
<ViewStub
|
|
||||||
android:id="@+id/messagePollStub"
|
|
||||||
style="@style/TimelineContentStubBaseParams"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="56dp"
|
|
||||||
android:layout="@layout/item_timeline_event_poll_stub" />
|
|
||||||
|
|
||||||
<ViewStub
|
|
||||||
android:id="@+id/messageOptionsStub"
|
|
||||||
style="@style/TimelineContentStubBaseParams"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="56dp"
|
|
||||||
android:layout="@layout/item_timeline_event_option_buttons_stub" />
|
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentVoiceStub"
|
android:id="@+id/messageContentVoiceStub"
|
||||||
style="@style/TimelineContentStubBaseParams"
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
@ -139,6 +125,12 @@
|
||||||
android:layout="@layout/item_timeline_event_voice_stub"
|
android:layout="@layout/item_timeline_event_voice_stub"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageContentPollStub"
|
||||||
|
style="@style/TimelineContentStubBaseParams"
|
||||||
|
android:layout="@layout/item_timeline_event_poll"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<im.vector.app.core.ui.views.SendStateImageView
|
<im.vector.app.core.ui.views.SendStateImageView
|
||||||
|
|
43
vector/src/main/res/layout/item_timeline_event_poll.xml
Normal file
43
vector/src/main/res/layout/item_timeline_event_poll.xml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/questionTextView"
|
||||||
|
style="@style/Widget.Vector.TextView.HeadlineMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:textColor="?vctr_content_primary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@sample/poll.json/question" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/optionsContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:divider="@drawable/divider_poll_options"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:showDividers="middle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/questionTextView" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/optionsTotalVotesTextView"
|
||||||
|
style="@style/Widget.Vector.TextView.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/optionsContainer"
|
||||||
|
tools:text="@sample/poll.json/totalVotes" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/pollItemContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical" />
|
|
@ -171,7 +171,6 @@
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:visibility="gone"
|
|
||||||
android:weightSum="3">
|
android:weightSum="3">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -3648,4 +3648,23 @@
|
||||||
<item quantity="one">At least %1$s option is required</item>
|
<item quantity="one">At least %1$s option is required</item>
|
||||||
<item quantity="other">At least %1$s options are required</item>
|
<item quantity="other">At least %1$s options are required</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="poll_option_vote_count">
|
||||||
|
<item quantity="one">%1$s vote</item>
|
||||||
|
<item quantity="other">%1$s votes</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="poll_total_vote_count_before_ended_and_voted">
|
||||||
|
<item quantity="one">Based on %1$s vote</item>
|
||||||
|
<item quantity="other">Based on %1$s votes</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="poll_total_vote_count_before_ended_and_not_voted">
|
||||||
|
<item quantity="zero">No votes cast</item>
|
||||||
|
<item quantity="one">%1$s vote cast. Vote to the see the results</item>
|
||||||
|
<item quantity="other">%1$s votes cast. Vote to the see the results</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="poll_total_vote_count_after_ended">
|
||||||
|
<item quantity="one">Final result based on %1$s vote</item>
|
||||||
|
<item quantity="other">Final result based on %1$s votes</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="poll_end_action">End poll</string>
|
||||||
|
<string name="a11y_poll_winner_option">winner option</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue