Undisclosed poll implementation.
This commit is contained in:
parent
7f97e78ba3
commit
b0b92c062e
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PollCreationInfo(
|
data class PollCreationInfo(
|
||||||
@Json(name = "question") val question: PollQuestion? = null,
|
@Json(name = "question") val question: PollQuestion? = null,
|
||||||
@Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed",
|
@Json(name = "kind") val kind: PollType? = PollType.DISCLOSED,
|
||||||
@Json(name = "max_selections") val maxSelections: Int = 1,
|
@Json(name = "max_selections") val maxSelections: Int = 1,
|
||||||
@Json(name = "answers") val answers: List<PollAnswer>? = null
|
@Json(name = "answers") val answers: List<PollAnswer>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
|
enum class PollType {
|
||||||
|
/**
|
||||||
|
* Voters should see results as soon as they have voted.
|
||||||
|
*/
|
||||||
|
@Json(name = "org.matrix.msc3381.poll.disclosed")
|
||||||
|
DISCLOSED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results should be only revealed when the poll is ended.
|
||||||
|
*/
|
||||||
|
@Json(name = "org.matrix.msc3381.poll.undisclosed")
|
||||||
|
UNDISCLOSED
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.relation
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
@ -66,11 +67,13 @@ interface RelationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit a poll.
|
* Edit a poll.
|
||||||
|
* @param pollType indicates open or closed polls
|
||||||
* @param targetEvent The poll event to edit
|
* @param targetEvent The poll event to edit
|
||||||
* @param question The edited question
|
* @param question The edited question
|
||||||
* @param options The edited options
|
* @param options The edited options
|
||||||
*/
|
*/
|
||||||
fun editPoll(targetEvent: TimelineEvent,
|
fun editPoll(targetEvent: TimelineEvent,
|
||||||
|
pollType: PollType,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>): Cancelable
|
options: List<String>): Cancelable
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -91,11 +92,12 @@ interface SendService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a poll to the room.
|
* Send a poll to the room.
|
||||||
|
* @param pollType indicates open or closed polls
|
||||||
* @param question the question
|
* @param question the question
|
||||||
* @param options list of options
|
* @param options list of options
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendPoll(question: String, options: List<String>): Cancelable
|
fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to send a poll response.
|
* Method to send a poll response.
|
||||||
|
|
|
@ -24,6 +24,7 @@ import dagger.assisted.AssistedInject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
@ -113,9 +114,10 @@ internal class DefaultRelationService @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editPoll(targetEvent: TimelineEvent,
|
override fun editPoll(targetEvent: TimelineEvent,
|
||||||
|
pollType: PollType,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>): Cancelable {
|
options: List<String>): Cancelable {
|
||||||
return eventEditor.editPoll(targetEvent, question, options)
|
return eventEditor.editPoll(targetEvent, pollType, question, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editTextMessage(targetEvent: TimelineEvent,
|
override fun editTextMessage(targetEvent: TimelineEvent,
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.relation
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
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.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
@ -59,17 +60,18 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editPoll(targetEvent: TimelineEvent,
|
fun editPoll(targetEvent: TimelineEvent,
|
||||||
|
pollType: PollType,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>): Cancelable {
|
options: List<String>): Cancelable {
|
||||||
val roomId = targetEvent.roomId
|
val roomId = targetEvent.roomId
|
||||||
if (targetEvent.root.sendState.hasFailed()) {
|
if (targetEvent.root.sendState.hasFailed()) {
|
||||||
val editedEvent = eventFactory.createPollEvent(roomId, question, options).copy(
|
val editedEvent = eventFactory.createPollEvent(roomId, pollType, question, options).copy(
|
||||||
eventId = targetEvent.eventId
|
eventId = targetEvent.eventId
|
||||||
)
|
)
|
||||||
return sendFailedEvent(targetEvent, editedEvent)
|
return sendFailedEvent(targetEvent, editedEvent)
|
||||||
} else if (targetEvent.root.sendState.isSent()) {
|
} else if (targetEvent.root.sendState.isSent()) {
|
||||||
val event = eventFactory
|
val event = eventFactory
|
||||||
.createPollReplaceEvent(roomId, targetEvent.eventId, question, options)
|
.createPollReplaceEvent(roomId, pollType, targetEvent.eventId, question, options)
|
||||||
return sendReplaceEvent(roomId, event)
|
return sendReplaceEvent(roomId, event)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Can't edit a sending event")
|
Timber.w("Can't edit a sending event")
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
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.MessageWithAttachmentContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendService
|
import org.matrix.android.sdk.api.session.room.send.SendService
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
@ -103,8 +104,8 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendPoll(question: String, options: List<String>): Cancelable {
|
override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable {
|
||||||
return localEchoEventFactory.createPollEvent(roomId, question, options)
|
return localEchoEventFactory.createPollEvent(roomId, pollType, question, options)
|
||||||
.also { createLocalEcho(it) }
|
.also { createLocalEcho(it) }
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollResponse
|
import org.matrix.android.sdk.api.session.room.model.message.PollResponse
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
|
import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
|
@ -126,12 +127,14 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPollContent(question: String,
|
private fun createPollContent(question: String,
|
||||||
options: List<String>): MessagePollContent {
|
options: List<String>,
|
||||||
|
pollType: PollType): MessagePollContent {
|
||||||
return MessagePollContent(
|
return MessagePollContent(
|
||||||
pollCreationInfo = PollCreationInfo(
|
pollCreationInfo = PollCreationInfo(
|
||||||
question = PollQuestion(
|
question = PollQuestion(
|
||||||
question = question
|
question = question
|
||||||
),
|
),
|
||||||
|
kind = pollType,
|
||||||
answers = options.map { option ->
|
answers = options.map { option ->
|
||||||
PollAnswer(
|
PollAnswer(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
|
@ -143,12 +146,13 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPollReplaceEvent(roomId: String,
|
fun createPollReplaceEvent(roomId: String,
|
||||||
|
pollType: PollType,
|
||||||
targetEventId: String,
|
targetEventId: String,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>): Event {
|
options: List<String>): Event {
|
||||||
val newContent = MessagePollContent(
|
val newContent = MessagePollContent(
|
||||||
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
||||||
newContent = createPollContent(question, options).toContent()
|
newContent = createPollContent(question, options, pollType).toContent()
|
||||||
)
|
)
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
|
@ -186,9 +190,10 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPollEvent(roomId: String,
|
fun createPollEvent(roomId: String,
|
||||||
|
pollType: PollType,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>): Event {
|
options: List<String>): Event {
|
||||||
val content = createPollContent(question, options)
|
val content = createPollContent(question, options, pollType)
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
|
|
@ -89,6 +89,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.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.PollType
|
||||||
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
|
||||||
|
@ -186,11 +187,14 @@ class MessageItemFactory @Inject constructor(
|
||||||
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
||||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
||||||
val isPollSent = informationData.sendState.isSent()
|
val isPollSent = informationData.sendState.isSent()
|
||||||
|
val isPollUndisclosed = pollContent.pollCreationInfo?.kind == PollType.UNDISCLOSED
|
||||||
|
|
||||||
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
|
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
|
||||||
when {
|
when {
|
||||||
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
|
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
|
||||||
didUserVoted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
|
isPollUndisclosed -> ""
|
||||||
else -> if (it == 0) {
|
didUserVoted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
|
||||||
|
else -> if (it == 0) {
|
||||||
stringProvider.getString(R.string.poll_no_votes_cast)
|
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
|
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
|
||||||
|
@ -214,6 +218,9 @@ class MessageItemFactory @Inject constructor(
|
||||||
// Poll is ended. Disable option, show votes and mark the winner.
|
// Poll is ended. Disable option, show votes and mark the winner.
|
||||||
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
||||||
PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
||||||
|
} else if (isPollUndisclosed) {
|
||||||
|
// Poll is closed. Enable option, hide votes and mark the user's selection.
|
||||||
|
PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
|
||||||
} else if (didUserVoted) {
|
} else if (didUserVoted) {
|
||||||
// User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
|
// User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
|
||||||
PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
||||||
|
|
|
@ -22,9 +22,9 @@ import androidx.core.view.children
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.extensions.setAttributeTintedImageResource
|
import im.vector.app.core.extensions.setAttributeTintedImageResource
|
||||||
import im.vector.app.databinding.ItemPollOptionBinding
|
import im.vector.app.databinding.ItemPollOptionBinding
|
||||||
|
|
||||||
|
@ -43,11 +44,12 @@ class PollOptionView @JvmOverloads constructor(
|
||||||
views.optionNameTextView.text = state.optionAnswer
|
views.optionNameTextView.text = state.optionAnswer
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is PollOptionViewState.PollSending -> renderPollSending()
|
is PollOptionViewState.PollSending -> renderPollSending()
|
||||||
is PollOptionViewState.PollEnded -> renderPollEnded(state)
|
is PollOptionViewState.PollEnded -> renderPollEnded(state)
|
||||||
is PollOptionViewState.PollReady -> renderPollReady()
|
is PollOptionViewState.PollReady -> renderPollReady()
|
||||||
is PollOptionViewState.PollVoted -> renderPollVoted(state)
|
is PollOptionViewState.PollVoted -> renderPollVoted(state)
|
||||||
}
|
is PollOptionViewState.PollUndisclosed -> renderPollUndisclosed(state)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderPollSending() {
|
private fun renderPollSending() {
|
||||||
|
@ -78,6 +80,12 @@ class PollOptionView @JvmOverloads constructor(
|
||||||
renderVoteSelection(state.isSelected)
|
renderVoteSelection(state.isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderPollUndisclosed(state: PollOptionViewState.PollUndisclosed) {
|
||||||
|
views.optionCheckImageView.isVisible = true
|
||||||
|
views.optionWinnerImageView.isVisible = false
|
||||||
|
renderVoteSelection(state.isSelected)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showVotes(voteCount: Int, votePercentage: Double) {
|
private fun showVotes(voteCount: Int, votePercentage: Double) {
|
||||||
views.optionVoteCountTextView.apply {
|
views.optionVoteCountTextView.apply {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
|
|
|
@ -51,4 +51,12 @@ sealed class PollOptionViewState(open val optionId: String,
|
||||||
val votePercentage: Double,
|
val votePercentage: Double,
|
||||||
val isWinner: Boolean
|
val isWinner: Boolean
|
||||||
) : PollOptionViewState(optionId, optionAnswer)
|
) : PollOptionViewState(optionId, optionAnswer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a poll that is closed, votes will be hidden until the poll is ended.
|
||||||
|
*/
|
||||||
|
data class PollUndisclosed(override val optionId: String,
|
||||||
|
override val optionAnswer: String,
|
||||||
|
val isSelected: Boolean
|
||||||
|
) : PollOptionViewState(optionId, optionAnswer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
package im.vector.app.features.poll.create
|
package im.vector.app.features.poll.create
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
sealed class CreatePollAction : VectorViewModelAction {
|
sealed class CreatePollAction : VectorViewModelAction {
|
||||||
data class OnQuestionChanged(val question: String) : CreatePollAction()
|
data class OnQuestionChanged(val question: String) : CreatePollAction()
|
||||||
data class OnOptionChanged(val index: Int, val option: String) : CreatePollAction()
|
data class OnOptionChanged(val index: Int, val option: String) : CreatePollAction()
|
||||||
data class OnDeleteOption(val index: Int) : CreatePollAction()
|
data class OnDeleteOption(val index: Int) : CreatePollAction()
|
||||||
|
data class OnPollTypeChanged(val pollType: PollType) : CreatePollAction()
|
||||||
object OnAddOption : CreatePollAction()
|
object OnAddOption : CreatePollAction()
|
||||||
object OnCreatePoll : CreatePollAction()
|
object OnCreatePoll : CreatePollAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.app.core.ui.list.genericItem
|
||||||
import im.vector.app.features.form.formEditTextItem
|
import im.vector.app.features.form.formEditTextItem
|
||||||
import im.vector.app.features.form.formEditTextWithDeleteItem
|
import im.vector.app.features.form.formEditTextWithDeleteItem
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreatePollController @Inject constructor(
|
class CreatePollController @Inject constructor(
|
||||||
|
@ -47,6 +48,26 @@ class CreatePollController @Inject constructor(
|
||||||
val currentState = state ?: return
|
val currentState = state ?: return
|
||||||
val host = this
|
val host = this
|
||||||
|
|
||||||
|
genericItem {
|
||||||
|
id("poll_type_title")
|
||||||
|
style(ItemStyle.BIG_TEXT)
|
||||||
|
title(host.stringProvider.getString(R.string.poll_type_title).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
pollTypeSelectionItem {
|
||||||
|
id("poll_type_selection")
|
||||||
|
pollType(currentState.pollType)
|
||||||
|
pollTypeChangedListener { _, id ->
|
||||||
|
host.callback?.onPollTypeChanged(
|
||||||
|
if (id == R.id.openPollTypeRadioButton) {
|
||||||
|
PollType.DISCLOSED
|
||||||
|
} else {
|
||||||
|
PollType.UNDISCLOSED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
genericItem {
|
genericItem {
|
||||||
id("question_title")
|
id("question_title")
|
||||||
style(ItemStyle.BIG_TEXT)
|
style(ItemStyle.BIG_TEXT)
|
||||||
|
@ -110,5 +131,6 @@ class CreatePollController @Inject constructor(
|
||||||
fun onOptionChanged(index: Int, option: String)
|
fun onOptionChanged(index: Int, option: String)
|
||||||
fun onDeleteOption(index: Int)
|
fun onDeleteOption(index: Int)
|
||||||
fun onAddOption()
|
fun onAddOption()
|
||||||
|
fun onPollTypeChanged(type: PollType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentCreatePollBinding
|
import im.vector.app.databinding.FragmentCreatePollBinding
|
||||||
import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
|
import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -60,18 +61,18 @@ class CreatePollFragment @Inject constructor(
|
||||||
|
|
||||||
when (args.mode) {
|
when (args.mode) {
|
||||||
PollMode.CREATE -> {
|
PollMode.CREATE -> {
|
||||||
views.createPollTitle.text = getString(R.string.create_poll_title)
|
views.createPollToolbar.title = getString(R.string.create_poll_title)
|
||||||
views.createPollButton.text = getString(R.string.create_poll_title)
|
views.createPollButton.text = getString(R.string.create_poll_title)
|
||||||
}
|
}
|
||||||
PollMode.EDIT -> {
|
PollMode.EDIT -> {
|
||||||
views.createPollTitle.text = getString(R.string.edit_poll_title)
|
views.createPollToolbar.title = getString(R.string.edit_poll_title)
|
||||||
views.createPollButton.text = getString(R.string.edit_poll_title)
|
views.createPollButton.text = getString(R.string.edit_poll_title)
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
|
|
||||||
views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true)
|
views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true)
|
||||||
// workaround for https://github.com/vector-im/element-android/issues/4735
|
// workaround for https://github.com/vector-im/element-android/issues/4735
|
||||||
views.createPollRecyclerView.setItemViewCacheSize(MAX_OPTIONS_COUNT + 4)
|
views.createPollRecyclerView.setItemViewCacheSize(MAX_OPTIONS_COUNT + 6)
|
||||||
controller.callback = this
|
controller.callback = this
|
||||||
|
|
||||||
views.createPollButton.debouncedClicks {
|
views.createPollButton.debouncedClicks {
|
||||||
|
@ -117,6 +118,10 @@ class CreatePollFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPollTypeChanged(type: PollType) {
|
||||||
|
viewModel.handle(CreatePollAction.OnPollTypeChanged(type))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSuccess() {
|
private fun handleSuccess() {
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
|
|
||||||
class CreatePollViewModel @AssistedInject constructor(
|
class CreatePollViewModel @AssistedInject constructor(
|
||||||
|
@ -70,13 +71,15 @@ class CreatePollViewModel @AssistedInject constructor(
|
||||||
val event = room.getTimeLineEvent(eventId) ?: return
|
val event = room.getTimeLineEvent(eventId) ?: return
|
||||||
val content = event.getLastMessageContent() as? MessagePollContent ?: return
|
val content = event.getLastMessageContent() as? MessagePollContent ?: return
|
||||||
|
|
||||||
|
val pollType = content.pollCreationInfo?.kind ?: PollType.DISCLOSED
|
||||||
val question = content.pollCreationInfo?.question?.question ?: ""
|
val question = content.pollCreationInfo?.question?.question ?: ""
|
||||||
val options = content.pollCreationInfo?.answers?.mapNotNull { it.answer } ?: List(MIN_OPTIONS_COUNT) { "" }
|
val options = content.pollCreationInfo?.answers?.mapNotNull { it.answer } ?: List(MIN_OPTIONS_COUNT) { "" }
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
question = question,
|
question = question,
|
||||||
options = options
|
options = options,
|
||||||
|
pollType = pollType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +91,7 @@ class CreatePollViewModel @AssistedInject constructor(
|
||||||
is CreatePollAction.OnDeleteOption -> handleOnDeleteOption(action.index)
|
is CreatePollAction.OnDeleteOption -> handleOnDeleteOption(action.index)
|
||||||
is CreatePollAction.OnOptionChanged -> handleOnOptionChanged(action.index, action.option)
|
is CreatePollAction.OnOptionChanged -> handleOnOptionChanged(action.index, action.option)
|
||||||
is CreatePollAction.OnQuestionChanged -> handleOnQuestionChanged(action.question)
|
is CreatePollAction.OnQuestionChanged -> handleOnQuestionChanged(action.question)
|
||||||
|
is CreatePollAction.OnPollTypeChanged -> handleOnPollTypeChanged(action.pollType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,17 +106,17 @@ class CreatePollViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
when (state.mode) {
|
when (state.mode) {
|
||||||
PollMode.CREATE -> room.sendPoll(state.question, nonEmptyOptions)
|
PollMode.CREATE -> room.sendPoll(state.pollType, state.question, nonEmptyOptions)
|
||||||
PollMode.EDIT -> sendEditedPoll(state.editedEventId!!, state.question, nonEmptyOptions)
|
PollMode.EDIT -> sendEditedPoll(state.editedEventId!!, state.pollType, state.question, nonEmptyOptions)
|
||||||
}
|
}
|
||||||
_viewEvents.post(CreatePollViewEvents.Success)
|
_viewEvents.post(CreatePollViewEvents.Success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendEditedPoll(editedEventId: String, question: String, options: List<String>) {
|
private fun sendEditedPoll(editedEventId: String, pollType: PollType, question: String, options: List<String>) {
|
||||||
val editedEvent = room.getTimeLineEvent(editedEventId) ?: return
|
val editedEvent = room.getTimeLineEvent(editedEventId) ?: return
|
||||||
room.editPoll(editedEvent, question, options)
|
room.editPoll(editedEvent, pollType, question, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOnAddOption() {
|
private fun handleOnAddOption() {
|
||||||
|
@ -150,6 +154,14 @@ class CreatePollViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleOnPollTypeChanged(pollType: PollType) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pollType = pollType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun canCreatePoll(question: String, options: List<String>): Boolean {
|
private fun canCreatePoll(question: String, options: List<String>): Boolean {
|
||||||
return question.isNotEmpty() &&
|
return question.isNotEmpty() &&
|
||||||
options.filter { it.isNotEmpty() }.size >= MIN_OPTIONS_COUNT
|
options.filter { it.isNotEmpty() }.size >= MIN_OPTIONS_COUNT
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.poll.create
|
package im.vector.app.features.poll.create
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
data class CreatePollViewState(
|
data class CreatePollViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
@ -25,7 +26,8 @@ data class CreatePollViewState(
|
||||||
val question: String = "",
|
val question: String = "",
|
||||||
val options: List<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
|
val options: List<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
|
||||||
val canCreatePoll: Boolean = false,
|
val canCreatePoll: Boolean = false,
|
||||||
val canAddMoreOptions: Boolean = true
|
val canAddMoreOptions: Boolean = true,
|
||||||
|
val pollType: PollType = PollType.DISCLOSED
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: CreatePollArgs) : this(
|
constructor(args: CreatePollArgs) : this(
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.poll.create
|
||||||
|
|
||||||
|
import android.widget.RadioGroup
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_poll_type_selection)
|
||||||
|
abstract class PollTypeSelectionItem : VectorEpoxyModel<PollTypeSelectionItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var pollType: PollType = PollType.DISCLOSED
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var pollTypeChangedListener: RadioGroup.OnCheckedChangeListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
|
||||||
|
holder.pollTypeRadioGroup.check(
|
||||||
|
when (pollType) {
|
||||||
|
PollType.DISCLOSED -> R.id.openPollTypeRadioButton
|
||||||
|
PollType.UNDISCLOSED -> R.id.closedPollTypeRadioButton
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
holder.pollTypeRadioGroup.setOnCheckedChangeListener(pollTypeChangedListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(holder: Holder) {
|
||||||
|
super.unbind(holder)
|
||||||
|
holder.pollTypeRadioGroup.setOnCheckedChangeListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val pollTypeRadioGroup by bind<RadioGroup>(R.id.pollTypeRadioGroup)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/pollTypeRadioGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/layout_vertical_margin">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/openPollTypeRadioButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/open_poll_option_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.Vector.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:text="@string/open_poll_option_description" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/closedPollTypeRadioButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:text="@string/closed_poll_option_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.Vector.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:text="@string/closed_poll_option_description" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
|
@ -3708,12 +3708,17 @@
|
||||||
<string name="end_poll_confirmation_description">This will stop people from being able to vote and will display the final results of the poll.</string>
|
<string name="end_poll_confirmation_description">This will stop people from being able to vote and will display the final results of the poll.</string>
|
||||||
<string name="end_poll_confirmation_approve_button">End poll</string>
|
<string name="end_poll_confirmation_approve_button">End poll</string>
|
||||||
<string name="labs_enable_polls">Enable Polls</string>
|
<string name="labs_enable_polls">Enable Polls</string>
|
||||||
<string name="poll_response_room_list_preview">Vote casted</string>
|
<string name="poll_response_room_list_preview">Vote cast</string>
|
||||||
<string name="poll_end_room_list_preview">Poll ended</string>
|
<string name="poll_end_room_list_preview">Poll ended</string>
|
||||||
<string name="delete_poll_dialog_title">Remove poll</string>
|
<string name="delete_poll_dialog_title">Remove poll</string>
|
||||||
<string name="delete_poll_dialog_content">Are you sure you want to remove this poll? You won\'t be able to recover it once removed.</string>
|
<string name="delete_poll_dialog_content">Are you sure you want to remove this poll? You won\'t be able to recover it once removed.</string>
|
||||||
<string name="edit_poll_title">Edit poll</string>
|
<string name="edit_poll_title">Edit poll</string>
|
||||||
<string name="edit_poll_button">EDIT POLL</string>
|
<string name="edit_poll_button">EDIT POLL</string>
|
||||||
|
<string name="poll_type_title">Poll type</string>
|
||||||
|
<string name="open_poll_option_title">Open poll</string>
|
||||||
|
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
|
||||||
|
<string name="closed_poll_option_title">Closed poll</string>
|
||||||
|
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
|
||||||
|
|
||||||
<string name="tooltip_attachment_photo">Open camera</string>
|
<string name="tooltip_attachment_photo">Open camera</string>
|
||||||
<string name="tooltip_attachment_gallery">Send images and videos</string>
|
<string name="tooltip_attachment_gallery">Send images and videos</string>
|
||||||
|
|
Loading…
Reference in New Issue