From 06485cf5e4061b5aab2efcb5a18ff492e2cd22f5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 3 Dec 2021 11:39:41 +0300 Subject: [PATCH] Implement poll in timeline ui. --- vector/sampledata/poll.json | 22 ++++ .../home/room/detail/RoomDetailAction.kt | 5 +- .../home/room/detail/RoomDetailFragment.kt | 3 + .../home/room/detail/RoomDetailViewModel.kt | 11 +- .../timeline/factory/MessageItemFactory.kt | 57 ++++------ .../timeline/factory/TimelineItemFactory.kt | 1 + .../helper/MessageInformationDataFactory.kt | 4 +- .../helper/TimelineDisplayableEvents.kt | 3 +- .../timeline/item/MessageInformationData.kt | 4 +- .../room/detail/timeline/item/PollItem.kt | 101 ++++++++++++++++++ .../detail/timeline/item/PollOptionItem.kt | 100 +++++++++++++++++ .../src/main/res/drawable/bg_poll_option.xml | 9 ++ .../res/drawable/divider_poll_options.xml | 8 ++ .../main/res/drawable/ic_check_on_white.xml | 13 +++ .../src/main/res/drawable/ic_poll_winner.xml | 9 ++ .../main/res/drawable/poll_option_checked.xml | 14 +++ .../poll_option_progressbar_checked.xml | 18 ++++ .../poll_option_progressbar_unchecked.xml | 18 ++++ .../res/drawable/poll_option_unchecked.xml | 13 +++ .../src/main/res/layout/item_poll_option.xml | 81 ++++++++++++++ .../res/layout/item_timeline_event_base.xml | 20 ++-- .../res/layout/item_timeline_event_poll.xml | 43 ++++++++ .../item_timeline_event_poll_container.xml | 6 ++ .../layout/view_attachment_type_selector.xml | 1 - vector/src/main/res/values/strings.xml | 19 ++++ 25 files changed, 521 insertions(+), 62 deletions(-) create mode 100644 vector/sampledata/poll.json create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionItem.kt create mode 100644 vector/src/main/res/drawable/bg_poll_option.xml create mode 100644 vector/src/main/res/drawable/divider_poll_options.xml create mode 100644 vector/src/main/res/drawable/ic_check_on_white.xml create mode 100644 vector/src/main/res/drawable/ic_poll_winner.xml create mode 100644 vector/src/main/res/drawable/poll_option_checked.xml create mode 100644 vector/src/main/res/drawable/poll_option_progressbar_checked.xml create mode 100644 vector/src/main/res/drawable/poll_option_progressbar_unchecked.xml create mode 100644 vector/src/main/res/drawable/poll_option_unchecked.xml create mode 100644 vector/src/main/res/layout/item_poll_option.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_poll.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_poll_container.xml diff --git a/vector/sampledata/poll.json b/vector/sampledata/poll.json new file mode 100644 index 0000000000..45fdf47b83 --- /dev/null +++ b/vector/sampledata/poll.json @@ -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" +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index a9b9f8000b..86fbd78f18 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -53,7 +53,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class RemoveFailedEcho(val eventId: String) : 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( val eventId: String, @@ -116,4 +116,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction() object PlayOrPauseRecordingPlayback : RoomDetailAction() data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction() + + // Poll + data class EndPoll(val eventId: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f0d7c6157e..93d88c9f51 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2017,6 +2017,9 @@ class RoomDetailFragment @Inject constructor( startActivity(KeysBackupRestoreActivity.intent(it)) } } + is EventSharedAction.EndPoll -> { + roomDetailViewModel.handle(RoomDetailAction.EndPoll(action.eventId)) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index d846a1d1f8..21d37c7f25 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -309,7 +309,7 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.RegisterVoteToPoll -> handleRegisterVoteToPoll(action) is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action) @@ -355,6 +355,7 @@ class RoomDetailViewModel @AssistedInject constructor( } _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true)) } + is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId) }.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 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() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 98deaaf9c3..32f94a125b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -23,6 +23,7 @@ import android.text.style.AbsoluteSizeSpan import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.view.View +import com.airbnb.epoxy.EpoxyModel import dagger.Lazy import im.vector.app.R 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.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.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.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.MessageImageInfoContent 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.MessagePollResponseContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent 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.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.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl @@ -125,7 +123,7 @@ class MessageItemFactory @Inject constructor( pillsPostProcessorFactory.create(roomId) } - fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): EpoxyModel<*>? { val event = params.event val highlight = params.isHighlighted val callback = params.callback @@ -168,41 +166,24 @@ class MessageItemFactory @Inject constructor( } } is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(params) + is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } - private fun buildOptionsMessageItem(messageContent: MessageOptionsContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { - return when (messageContent.optionType) { - OPTION_TYPE_POLL -> { - MessagePollItem_() - .attributes(attributes) - .callback(callback) - .informationData(informationData) - .leftGuideline(avatarSizeProvider.leftGuideline) - .optionsContent(messageContent) - .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 buildPollContent(messageContent: MessagePollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes): PollItem? { + return PollItem_() + .attributes(attributes) + .eventId(informationData.eventId) + .pollResponseSummary(informationData.pollResponseAggregatedSummary) + .pollContent(messageContent) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) } private fun buildAudioMessageItem(messageContent: MessageAudioContent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index c21fe935bb..9e95dff26b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -48,6 +48,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me when (event.root.getClearType()) { // Message itemsX EventType.STICKER, + EventType.POLL_START, EventType.MESSAGE -> messageItemFactory.create(params) EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_NAME, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 6385494fe1..b4ed0c3e94 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -107,9 +107,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let { PollResponseData( myVote = it.aggregatedContent?.myVote, - isClosed = it.closedTime ?: Long.MAX_VALUE > System.currentTimeMillis(), + isClosed = it.closedTime != null, votes = it.aggregatedContent?.votes - ?.groupBy({ it.optionIndex }, { it.userId }) + ?.groupBy({ it.option }, { it.userId }) ?.mapValues { it.value.size } ) }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 053b804a82..bcccbc9f7c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -50,7 +50,8 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, - EventType.KEY_VERIFICATION_CANCEL + EventType.KEY_VERIFICATION_CANCEL, + EventType.POLL_START ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 08aa301538..125133d3f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -71,8 +71,8 @@ data class ReadReceiptData( @Parcelize data class PollResponseData( - val myVote: Int?, - val votes: Map?, + val myVote: String?, + val votes: Map?, val isClosed: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt new file mode 100644 index 0000000000..1643c0f48a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt @@ -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() { + + @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(R.id.questionTextView) + val optionsContainer by bind(R.id.optionsContainer) + val totalVotesTextView by bind(R.id.optionsTotalVotesTextView) + } + + companion object { + private const val STUB_ID = R.id.messageContentPollStub + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionItem.kt new file mode 100644 index 0000000000..aed1210532 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionItem.kt @@ -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 + } + } +} diff --git a/vector/src/main/res/drawable/bg_poll_option.xml b/vector/src/main/res/drawable/bg_poll_option.xml new file mode 100644 index 0000000000..52b420f058 --- /dev/null +++ b/vector/src/main/res/drawable/bg_poll_option.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/divider_poll_options.xml b/vector/src/main/res/drawable/divider_poll_options.xml new file mode 100644 index 0000000000..689e958bc3 --- /dev/null +++ b/vector/src/main/res/drawable/divider_poll_options.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_check_on_white.xml b/vector/src/main/res/drawable/ic_check_on_white.xml new file mode 100644 index 0000000000..1b29129c1b --- /dev/null +++ b/vector/src/main/res/drawable/ic_check_on_white.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/drawable/ic_poll_winner.xml b/vector/src/main/res/drawable/ic_poll_winner.xml new file mode 100644 index 0000000000..71bc7ee5b6 --- /dev/null +++ b/vector/src/main/res/drawable/ic_poll_winner.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/poll_option_checked.xml b/vector/src/main/res/drawable/poll_option_checked.xml new file mode 100644 index 0000000000..28ab94a421 --- /dev/null +++ b/vector/src/main/res/drawable/poll_option_checked.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/poll_option_progressbar_checked.xml b/vector/src/main/res/drawable/poll_option_progressbar_checked.xml new file mode 100644 index 0000000000..7bc74cb765 --- /dev/null +++ b/vector/src/main/res/drawable/poll_option_progressbar_checked.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/poll_option_progressbar_unchecked.xml b/vector/src/main/res/drawable/poll_option_progressbar_unchecked.xml new file mode 100644 index 0000000000..e327c66caa --- /dev/null +++ b/vector/src/main/res/drawable/poll_option_progressbar_unchecked.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/poll_option_unchecked.xml b/vector/src/main/res/drawable/poll_option_unchecked.xml new file mode 100644 index 0000000000..dd50bde256 --- /dev/null +++ b/vector/src/main/res/drawable/poll_option_unchecked.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_poll_option.xml b/vector/src/main/res/layout/item_poll_option.xml new file mode 100644 index 0000000000..15f253d0b9 --- /dev/null +++ b/vector/src/main/res/layout/item_poll_option.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index cb6f701bb4..0e084bb474 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -118,20 +118,6 @@ android:layout_marginEnd="56dp" android:layout="@layout/item_timeline_event_redacted_stub" /> - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_poll_container.xml b/vector/src/main/res/layout/item_timeline_event_poll_container.xml new file mode 100644 index 0000000000..bae7ac3c3a --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_poll_container.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index bc747778a7..82fe40eba0 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -171,7 +171,6 @@ android:layout_margin="16dp" android:baselineAligned="false" android:orientation="horizontal" - android:visibility="gone" android:weightSum="3"> At least %1$s option is required At least %1$s options are required + + %1$s vote + %1$s votes + + + Based on %1$s vote + Based on %1$s votes + + + No votes cast + %1$s vote cast. Vote to the see the results + %1$s votes cast. Vote to the see the results + + + Final result based on %1$s vote + Final result based on %1$s votes + + End poll + winner option