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 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()
|
||||
}
|
||||
|
|
|
@ -2017,6 +2017,9 @@ class RoomDetailFragment @Inject constructor(
|
|||
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.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() {
|
||||
|
|
|
@ -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,
|
||||
private fun buildPollContent(messageContent: MessagePollContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
return when (messageContent.optionType) {
|
||||
OPTION_TYPE_POLL -> {
|
||||
MessagePollItem_()
|
||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
||||
return PollItem_()
|
||||
.attributes(attributes)
|
||||
.callback(callback)
|
||||
.informationData(informationData)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.optionsContent(messageContent)
|
||||
.eventId(informationData.eventId)
|
||||
.pollResponseSummary(informationData.pollResponseAggregatedSummary)
|
||||
.pollContent(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)
|
||||
}
|
||||
}
|
||||
.callback(callback)
|
||||
}
|
||||
|
||||
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }
|
||||
)
|
||||
},
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ data class ReadReceiptData(
|
|||
|
||||
@Parcelize
|
||||
data class PollResponseData(
|
||||
val myVote: Int?,
|
||||
val votes: Map<Int, Int>?,
|
||||
val myVote: String?,
|
||||
val votes: Map<String, Int>?,
|
||||
val isClosed: Boolean = false
|
||||
) : 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="@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
|
||||
android:id="@+id/messageContentVoiceStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
|
@ -139,6 +125,12 @@
|
|||
android:layout="@layout/item_timeline_event_voice_stub"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageContentPollStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_poll"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<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:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
android:weightSum="3">
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -3648,4 +3648,23 @@
|
|||
<item quantity="one">At least %1$s option is required</item>
|
||||
<item quantity="other">At least %1$s options are required</item>
|
||||
</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>
|
||||
|
|
Loading…
Reference in a new issue