mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Refactor poll item view state factory.
This commit is contained in:
parent
2be43e9294
commit
532bc18b1e
6 changed files with 223 additions and 428 deletions
|
@ -53,6 +53,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollItem
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
|
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
||||||
|
@ -128,7 +130,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val urlMapProvider: UrlMapProvider,
|
private val urlMapProvider: UrlMapProvider,
|
||||||
private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory,
|
private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory,
|
||||||
private val pollItemFactory: PollItemFactory,
|
private val pollItemViewStateFactory: PollItemViewStateFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO inject this properly?
|
// TODO inject this properly?
|
||||||
|
@ -188,7 +190,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
||||||
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
|
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
|
||||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessagePollContent -> pollItemFactory.create(messageContent, informationData, highlight, callback, attributes)
|
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
|
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
|
||||||
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
|
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
@ -224,6 +226,28 @@ class MessageItemFactory @Inject constructor(
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildPollItem(
|
||||||
|
pollContent: MessagePollContent,
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): PollItem {
|
||||||
|
val pollViewState = pollItemViewStateFactory.create(pollContent, informationData, callback)
|
||||||
|
|
||||||
|
return PollItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.eventId(informationData.eventId)
|
||||||
|
.pollQuestion(pollViewState.question)
|
||||||
|
.canVote(pollViewState.canVote)
|
||||||
|
.totalVotesText(pollViewState.totalVotes)
|
||||||
|
.optionViewStates(pollViewState.optionViewStates)
|
||||||
|
.edited(informationData.hasBeenEdited)
|
||||||
|
.highlighted(highlight)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
.callback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildAudioMessageItem(
|
private fun buildAudioMessageItem(
|
||||||
params: TimelineItemFactoryParams,
|
params: TimelineItemFactoryParams,
|
||||||
messageContent: MessageAudioContent,
|
messageContent: MessageAudioContent,
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.home.room.detail.timeline.factory
|
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.MessageItemFactoryHelper.annotateWithEdited
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollItem_
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
|
||||||
import im.vector.app.features.poll.PollState
|
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class PollItemFactory @Inject constructor(
|
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
|
||||||
private val colorProvider: ColorProvider,
|
|
||||||
private val dimensionConverter: DimensionConverter,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun create(
|
|
||||||
pollContent: MessagePollContent,
|
|
||||||
informationData: MessageInformationData,
|
|
||||||
highlight: Boolean,
|
|
||||||
callback: TimelineEventController.Callback?,
|
|
||||||
attributes: AbsMessageItem.Attributes,
|
|
||||||
): VectorEpoxyModel<*>? {
|
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
|
||||||
val pollState = createPollState(informationData, pollResponseSummary, pollContent)
|
|
||||||
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
|
||||||
val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
|
||||||
val question = createPollQuestion(informationData, questionText, callback)
|
|
||||||
val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
|
|
||||||
val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
|
|
||||||
|
|
||||||
return PollItem_()
|
|
||||||
.attributes(attributes)
|
|
||||||
.eventId(informationData.eventId)
|
|
||||||
.pollQuestion(question)
|
|
||||||
.canVote(pollState.isVotable())
|
|
||||||
.totalVotesText(totalVotesText)
|
|
||||||
.optionViewStates(optionViewStates)
|
|
||||||
.edited(informationData.hasBeenEdited)
|
|
||||||
.highlighted(highlight)
|
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
|
||||||
.callback(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
||||||
internal fun createPollState(
|
|
||||||
informationData: MessageInformationData,
|
|
||||||
pollResponseSummary: PollResponseData?,
|
|
||||||
pollContent: MessagePollContent,
|
|
||||||
): PollState = when {
|
|
||||||
!informationData.sendState.isSent() -> PollState.Sending
|
|
||||||
pollResponseSummary?.isClosed.orFalse() -> PollState.Ended
|
|
||||||
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> PollState.Undisclosed
|
|
||||||
pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> PollState.Voted(pollResponseSummary?.totalVotes ?: 0)
|
|
||||||
else -> PollState.Ready
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
||||||
internal fun List<PollAnswer>.mapToOptions(
|
|
||||||
pollState: PollState,
|
|
||||||
informationData: MessageInformationData,
|
|
||||||
) = map { answer ->
|
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
|
||||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
|
||||||
val optionId = answer.id ?: ""
|
|
||||||
val optionAnswer = answer.getBestAnswer() ?: ""
|
|
||||||
val voteSummary = pollResponseSummary?.votes?.get(answer.id)
|
|
||||||
val voteCount = voteSummary?.total ?: 0
|
|
||||||
val votePercentage = voteSummary?.percentage ?: 0.0
|
|
||||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
|
||||||
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
|
||||||
|
|
||||||
when (pollState) {
|
|
||||||
PollState.Sending -> PollOptionViewState.PollSending(optionId, optionAnswer)
|
|
||||||
PollState.Ready -> PollOptionViewState.PollReady(optionId, optionAnswer)
|
|
||||||
is PollState.Voted -> PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
|
||||||
PollState.Undisclosed -> PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
|
|
||||||
PollState.Ended -> PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPollQuestion(
|
|
||||||
informationData: MessageInformationData,
|
|
||||||
question: String,
|
|
||||||
callback: TimelineEventController.Callback?,
|
|
||||||
) = if (informationData.hasBeenEdited) {
|
|
||||||
annotateWithEdited(stringProvider, colorProvider, dimensionConverter, question, callback, informationData)
|
|
||||||
} else {
|
|
||||||
question
|
|
||||||
}.toEpoxyCharSequence()
|
|
||||||
|
|
||||||
private fun createTotalVotesText(
|
|
||||||
pollState: PollState,
|
|
||||||
pollResponseSummary: PollResponseData?,
|
|
||||||
): String {
|
|
||||||
val votes = pollResponseSummary?.totalVotes ?: 0
|
|
||||||
return when {
|
|
||||||
pollState is PollState.Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
|
|
||||||
pollState is PollState.Undisclosed -> ""
|
|
||||||
pollState is PollState.Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes)
|
|
||||||
votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast)
|
|
||||||
else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
* 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.home.room.detail.timeline.factory
|
||||||
|
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.factory.MessageItemFactoryHelper.annotateWithEdited
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import im.vector.app.features.poll.PollViewState
|
||||||
|
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||||
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PollItemViewStateFactory @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val colorProvider: ColorProvider,
|
||||||
|
private val dimensionConverter: DimensionConverter,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
pollContent: MessagePollContent,
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
): PollViewState {
|
||||||
|
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
||||||
|
|
||||||
|
val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
|
val question = createPollQuestion(informationData, questionText, callback)
|
||||||
|
|
||||||
|
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||||
|
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
||||||
|
val totalVotes = pollResponseSummary?.totalVotes ?: 0
|
||||||
|
|
||||||
|
return when {
|
||||||
|
!informationData.sendState.isSent() -> {
|
||||||
|
createSendingPollViewState(question, pollCreationInfo)
|
||||||
|
}
|
||||||
|
informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> {
|
||||||
|
createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes, winnerVoteCount)
|
||||||
|
}
|
||||||
|
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
|
||||||
|
createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary)
|
||||||
|
}
|
||||||
|
informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> {
|
||||||
|
createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
createReadyPollViewState(question, pollCreationInfo, totalVotes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSendingPollViewState(question: EpoxyCharSequence, pollCreationInfo: PollCreationInfo?): PollViewState {
|
||||||
|
return PollViewState(
|
||||||
|
question = question,
|
||||||
|
totalVotes = stringProvider.getString(R.string.poll_no_votes_cast),
|
||||||
|
canVote = false,
|
||||||
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollSending(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: ""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEndedPollViewState(
|
||||||
|
question: EpoxyCharSequence,
|
||||||
|
pollCreationInfo: PollCreationInfo?,
|
||||||
|
pollResponseSummary: PollResponseData?,
|
||||||
|
totalVotes: Int,
|
||||||
|
winnerVoteCount: Int?,
|
||||||
|
): PollViewState {
|
||||||
|
return PollViewState(
|
||||||
|
question = question,
|
||||||
|
totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes),
|
||||||
|
canVote = false,
|
||||||
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
PollOptionViewState.PollEnded(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: "",
|
||||||
|
voteCount = voteSummary?.total ?: 0,
|
||||||
|
votePercentage = voteSummary?.percentage ?: 0.0,
|
||||||
|
isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUndisclosedPollViewState(
|
||||||
|
question: EpoxyCharSequence,
|
||||||
|
pollCreationInfo: PollCreationInfo?,
|
||||||
|
pollResponseSummary: PollResponseData?
|
||||||
|
): PollViewState {
|
||||||
|
return PollViewState(
|
||||||
|
question = question,
|
||||||
|
totalVotes = "",
|
||||||
|
canVote = true,
|
||||||
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val isMyVote = pollResponseSummary?.myVote == answer.id
|
||||||
|
PollOptionViewState.PollUndisclosed(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: "",
|
||||||
|
isSelected = isMyVote
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createVotedPollViewState(
|
||||||
|
question: EpoxyCharSequence,
|
||||||
|
pollCreationInfo: PollCreationInfo?,
|
||||||
|
pollResponseSummary: PollResponseData?,
|
||||||
|
totalVotes: Int
|
||||||
|
): PollViewState {
|
||||||
|
return PollViewState(
|
||||||
|
question = question,
|
||||||
|
totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes),
|
||||||
|
canVote = true,
|
||||||
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val isMyVote = pollResponseSummary?.myVote == answer.id
|
||||||
|
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
PollOptionViewState.PollVoted(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: "",
|
||||||
|
voteCount = voteSummary?.total ?: 0,
|
||||||
|
votePercentage = voteSummary?.percentage ?: 0.0,
|
||||||
|
isSelected = isMyVote
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createReadyPollViewState(question: EpoxyCharSequence, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState {
|
||||||
|
val totalVotesText = if (totalVotes == 0) {
|
||||||
|
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||||
|
} else {
|
||||||
|
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes)
|
||||||
|
}
|
||||||
|
return PollViewState(
|
||||||
|
question = question,
|
||||||
|
totalVotes = totalVotesText,
|
||||||
|
canVote = true,
|
||||||
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
PollOptionViewState.PollReady(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: ""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPollQuestion(
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
question: String,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
) = if (informationData.hasBeenEdited) {
|
||||||
|
annotateWithEdited(stringProvider, colorProvider, dimensionConverter, question, callback, informationData)
|
||||||
|
} else {
|
||||||
|
question
|
||||||
|
}.toEpoxyCharSequence()
|
||||||
|
}
|
|
@ -91,7 +91,10 @@ data class PollResponseData(
|
||||||
val totalVotes: Int = 0,
|
val totalVotes: Int = 0,
|
||||||
val winnerVoteCount: Int = 0,
|
val winnerVoteCount: Int = 0,
|
||||||
val isClosed: Boolean = false
|
val isClosed: Boolean = false
|
||||||
) : Parcelable
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId)
|
||||||
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PollVoteSummaryData(
|
data class PollVoteSummaryData(
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
package im.vector.app.features.poll
|
package im.vector.app.features.poll
|
||||||
|
|
||||||
sealed interface PollState {
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
object Sending : PollState
|
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||||
object Ready : PollState
|
|
||||||
data class Voted(val votes: Int) : PollState
|
|
||||||
object Undisclosed : PollState
|
|
||||||
object Ended : PollState
|
|
||||||
|
|
||||||
fun isVotable() = this !is Sending && this !is Ended
|
data class PollViewState(
|
||||||
}
|
val question: EpoxyCharSequence,
|
||||||
|
val totalVotes: String,
|
||||||
|
val canVote: Boolean,
|
||||||
|
val optionViewStates: List<PollOptionViewState>?,
|
||||||
|
)
|
|
@ -1,282 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.home.room.detail.timeline.factory
|
|
||||||
|
|
||||||
import com.airbnb.mvrx.test.MvRxTestRule
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
|
||||||
import im.vector.app.features.poll.PollState
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.unmockkAll
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.amshove.kluent.shouldBe
|
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
|
||||||
|
|
||||||
private val A_MESSAGE_INFORMATION_DATA = MessageInformationData(
|
|
||||||
eventId = "eventId",
|
|
||||||
senderId = "senderId",
|
|
||||||
ageLocalTS = 0,
|
|
||||||
avatarUrl = "",
|
|
||||||
sendState = SendState.SENT,
|
|
||||||
messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true),
|
|
||||||
reactionsSummary = ReactionsSummaryData(),
|
|
||||||
sentByMe = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val A_POLL_RESPONSE_DATA = PollResponseData(
|
|
||||||
myVote = null,
|
|
||||||
votes = emptyMap(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val A_POLL_CONTENT = MessagePollContent(
|
|
||||||
unstablePollCreationInfo = PollCreationInfo(
|
|
||||||
question = PollQuestion(
|
|
||||||
unstableQuestion = "What is your favourite coffee?"
|
|
||||||
),
|
|
||||||
kind = PollType.UNDISCLOSED_UNSTABLE,
|
|
||||||
maxSelections = 1,
|
|
||||||
answers = listOf(
|
|
||||||
PollAnswer(
|
|
||||||
id = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76",
|
|
||||||
unstableAnswer = "Double Espresso"
|
|
||||||
),
|
|
||||||
PollAnswer(
|
|
||||||
id = "ec1a4db0-46d8-4d7a-9bb6-d80724715938",
|
|
||||||
unstableAnswer = "Macchiato"
|
|
||||||
),
|
|
||||||
PollAnswer(
|
|
||||||
id = "3677ca8e-061b-40ab-bffe-b22e4e88fcad",
|
|
||||||
unstableAnswer = "Iced Coffee"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
class PollItemFactoryTest {
|
|
||||||
|
|
||||||
private val testDispatcher = UnconfinedTestDispatcher()
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val mvRxTestRule = MvRxTestRule(
|
|
||||||
testDispatcher = testDispatcher // See https://github.com/airbnb/mavericks/issues/599
|
|
||||||
)
|
|
||||||
|
|
||||||
private lateinit var pollItemFactory: PollItemFactory
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
// We are not going to test any UI related code
|
|
||||||
pollItemFactory = PollItemFactory(
|
|
||||||
stringProvider = mockk(),
|
|
||||||
avatarSizeProvider = mockk(),
|
|
||||||
colorProvider = mockk(),
|
|
||||||
dimensionConverter = mockk(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
unmockkAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sending poll state then PollState is Sending`() = runTest {
|
|
||||||
val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING)
|
|
||||||
pollItemFactory.createPollState(
|
|
||||||
informationData = sendingPollInformationData,
|
|
||||||
pollResponseSummary = A_POLL_RESPONSE_DATA,
|
|
||||||
pollContent = A_POLL_CONTENT,
|
|
||||||
) shouldBe PollState.Sending
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll state when poll is closed then PollState is Ended`() = runTest {
|
|
||||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
|
|
||||||
|
|
||||||
pollItemFactory.createPollState(
|
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
|
||||||
pollResponseSummary = closedPollSummary,
|
|
||||||
pollContent = A_POLL_CONTENT,
|
|
||||||
) shouldBe PollState.Ended
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll when undisclosed poll type is selected then PollState is Undisclosed`() = runTest {
|
|
||||||
pollItemFactory.createPollState(
|
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
|
||||||
pollResponseSummary = A_POLL_RESPONSE_DATA,
|
|
||||||
pollContent = A_POLL_CONTENT,
|
|
||||||
) shouldBe PollState.Undisclosed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll when my vote exists then PollState is Voted`() = runTest {
|
|
||||||
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
|
||||||
totalVotes = 1,
|
|
||||||
myVote = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76",
|
|
||||||
)
|
|
||||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
|
||||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
pollItemFactory.createPollState(
|
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
|
||||||
pollResponseSummary = votedPollData,
|
|
||||||
pollContent = disclosedPollContent,
|
|
||||||
) shouldBeEqualTo PollState.Voted(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll when poll type is disclosed then PollState is Ready`() = runTest {
|
|
||||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
|
||||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
pollItemFactory.createPollState(
|
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
|
||||||
pollResponseSummary = A_POLL_RESPONSE_DATA,
|
|
||||||
pollContent = disclosedPollContent,
|
|
||||||
) shouldBe PollState.Ready
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sending poll then all option view states is PollSending`() = runTest {
|
|
||||||
with(pollItemFactory) {
|
|
||||||
A_POLL_CONTENT
|
|
||||||
.getBestPollCreationInfo()
|
|
||||||
?.answers
|
|
||||||
?.mapToOptions(PollState.Sending, A_MESSAGE_INFORMATION_DATA)
|
|
||||||
?.forEachIndexed { index, pollOptionViewState ->
|
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option ->
|
|
||||||
pollOptionViewState shouldBeEqualTo PollOptionViewState.PollSending(option.id ?: "", option.getBestAnswer() ?: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll then all option view states is PollReady`() = runTest {
|
|
||||||
with(pollItemFactory) {
|
|
||||||
A_POLL_CONTENT
|
|
||||||
.getBestPollCreationInfo()
|
|
||||||
?.answers
|
|
||||||
?.mapToOptions(PollState.Sending, A_MESSAGE_INFORMATION_DATA)
|
|
||||||
?.forEachIndexed { index, pollOptionViewState ->
|
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option ->
|
|
||||||
pollOptionViewState shouldBeEqualTo PollOptionViewState.PollSending(option.id ?: "", option.getBestAnswer() ?: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll when a vote is cast then all option view states is PollVoted`() = runTest {
|
|
||||||
with(pollItemFactory) {
|
|
||||||
A_POLL_CONTENT
|
|
||||||
.getBestPollCreationInfo()
|
|
||||||
?.answers
|
|
||||||
?.mapToOptions(PollState.Voted(1), A_MESSAGE_INFORMATION_DATA)
|
|
||||||
?.forEachIndexed { index, pollOptionViewState ->
|
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option ->
|
|
||||||
val voteSummary = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.votes?.get(option.id)
|
|
||||||
pollOptionViewState shouldBeEqualTo PollOptionViewState.PollVoted(
|
|
||||||
optionId = option.id ?: "",
|
|
||||||
optionAnswer = option.getBestAnswer() ?: "",
|
|
||||||
voteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.totalVotes ?: 0,
|
|
||||||
votePercentage = voteSummary?.percentage ?: 0.0,
|
|
||||||
isSelected = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.myVote == option.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a sent poll when the poll is undisclosed then all option view states is PollUndisclosed`() = runTest {
|
|
||||||
with(pollItemFactory) {
|
|
||||||
A_POLL_CONTENT
|
|
||||||
.getBestPollCreationInfo()
|
|
||||||
?.answers
|
|
||||||
?.mapToOptions(PollState.Undisclosed, A_MESSAGE_INFORMATION_DATA)
|
|
||||||
?.forEachIndexed { index, pollOptionViewState ->
|
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option ->
|
|
||||||
pollOptionViewState shouldBeEqualTo PollOptionViewState.PollUndisclosed(
|
|
||||||
optionId = option.id ?: "",
|
|
||||||
optionAnswer = option.getBestAnswer() ?: "",
|
|
||||||
isSelected = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.myVote == option.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given an ended poll then all option view states is Ended`() = runTest {
|
|
||||||
with(pollItemFactory) {
|
|
||||||
A_POLL_CONTENT
|
|
||||||
.getBestPollCreationInfo()
|
|
||||||
?.answers
|
|
||||||
?.mapToOptions(PollState.Ended, A_MESSAGE_INFORMATION_DATA)
|
|
||||||
?.forEachIndexed { index, pollOptionViewState ->
|
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option ->
|
|
||||||
val voteSummary = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.votes?.get(option.id)
|
|
||||||
val voteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.totalVotes ?: 0
|
|
||||||
val winnerVoteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.winnerVoteCount ?: 0
|
|
||||||
pollOptionViewState shouldBeEqualTo PollOptionViewState.PollEnded(
|
|
||||||
optionId = option.id ?: "",
|
|
||||||
optionAnswer = option.getBestAnswer() ?: "",
|
|
||||||
voteCount = voteCount,
|
|
||||||
votePercentage = voteSummary?.percentage ?: 0.0,
|
|
||||||
isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `given a poll state when it is not Sending and not Ended then the poll is votable`() = runTest {
|
|
||||||
val sendingPollState = PollState.Sending
|
|
||||||
sendingPollState.isVotable() shouldBe false
|
|
||||||
val readyPollState = PollState.Ready
|
|
||||||
readyPollState.isVotable() shouldBe true
|
|
||||||
val votedPollState = PollState.Voted(1)
|
|
||||||
votedPollState.isVotable() shouldBe true
|
|
||||||
val undisclosedPollState = PollState.Undisclosed
|
|
||||||
undisclosedPollState.isVotable() shouldBe true
|
|
||||||
var endedPollState = PollState.Ended
|
|
||||||
endedPollState.isVotable() shouldBe false
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue