mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 03:16:02 +03:00
Merge pull request #5473 from vector-im/bugfix/eric/voting-ended-poll
Fixes ended poll voting
This commit is contained in:
commit
10974366fb
12 changed files with 481 additions and 402 deletions
1
changelog.d/5473.bugfix
Normal file
1
changelog.d/5473.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fixes polls being votable after being ended
|
|
@ -482,46 +482,39 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
isLocalEcho: Boolean) {
|
isLocalEcho: Boolean) {
|
||||||
val pollEventId = content.relatesTo?.eventId ?: return
|
val pollEventId = content.relatesTo?.eventId ?: return
|
||||||
|
|
||||||
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
||||||
val isPollOwner = pollOwnerId == event.senderId
|
val isPollOwner = pollOwnerId == event.senderId
|
||||||
|
|
||||||
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||||
?.content?.toModel<PowerLevelsContent>()
|
?.content?.toModel<PowerLevelsContent>()
|
||||||
?.let { PowerLevelsHelper(it) }
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
|
||||||
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
||||||
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||||
if (existing == null) {
|
if (existingPoll == null) {
|
||||||
Timber.v("## POLL creating new relation summary for $pollEventId")
|
Timber.v("## POLL creating new relation summary for $pollEventId")
|
||||||
existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have it
|
// we have it
|
||||||
val existingPollSummary = existing.pollResponseSummary
|
val existingPollSummary = existingPoll.pollResponseSummary
|
||||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||||
existing.pollResponseSummary = it
|
existingPoll.pollResponseSummary = it
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingPollSummary.closedTime != null) {
|
|
||||||
Timber.v("## Received poll.end event for already ended poll $pollEventId")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
|
existingPollSummary.closedTime = event.originServerTs
|
||||||
|
|
||||||
// is it a remote echo?
|
// is it a remote echo?
|
||||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||||
// ok it has already been managed
|
// ok it has already been managed
|
||||||
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
||||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||||
existingPollSummary.sourceEvents.add(event.eventId)
|
existingPollSummary.sourceEvents.add(event.eventId)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
existingPollSummary.closedTime = event.originServerTs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
||||||
|
|
|
@ -183,7 +183,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.permalink.NavigationInterceptor
|
import im.vector.app.features.permalink.NavigationInterceptor
|
||||||
import im.vector.app.features.permalink.PermalinkHandler
|
import im.vector.app.features.permalink.PermalinkHandler
|
||||||
import im.vector.app.features.poll.create.PollMode
|
import im.vector.app.features.poll.PollMode
|
||||||
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
||||||
import im.vector.app.features.roomprofile.RoomProfileActivity
|
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
|
|
|
@ -56,7 +56,12 @@ 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.PollItem_
|
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.PollOptionViewState.PollEnded
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollReady
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollSending
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollUndisclosed
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollVoted
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
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
|
||||||
|
@ -73,6 +78,12 @@ import im.vector.app.features.location.UrlMapProvider
|
||||||
import im.vector.app.features.location.toLocationData
|
import im.vector.app.features.location.toLocationData
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.media.VideoContentRenderer
|
import im.vector.app.features.media.VideoContentRenderer
|
||||||
|
import im.vector.app.features.poll.PollState
|
||||||
|
import im.vector.app.features.poll.PollState.Ended
|
||||||
|
import im.vector.app.features.poll.PollState.Ready
|
||||||
|
import im.vector.app.features.poll.PollState.Sending
|
||||||
|
import im.vector.app.features.poll.PollState.Undisclosed
|
||||||
|
import im.vector.app.features.poll.PollState.Voted
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.voice.AudioWaveformView
|
import im.vector.app.features.voice.AudioWaveformView
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
@ -96,6 +107,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.PollAnswer
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
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
|
||||||
|
@ -108,30 +120,30 @@ import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsS
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MessageItemFactory @Inject constructor(
|
class MessageItemFactory @Inject constructor(
|
||||||
private val localFilesHelper: LocalFilesHelper,
|
private val localFilesHelper: LocalFilesHelper,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||||
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
||||||
private val htmlCompressor: VectorHtmlCompressor,
|
private val htmlCompressor: VectorHtmlCompressor,
|
||||||
private val textRendererFactory: EventTextRenderer.Factory,
|
private val textRendererFactory: EventTextRenderer.Factory,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||||
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
|
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
|
||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||||
private val spanUtils: SpanUtils,
|
private val spanUtils: SpanUtils,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
|
private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
|
||||||
private val locationPinProvider: LocationPinProvider,
|
private val locationPinProvider: LocationPinProvider,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val urlMapProvider: UrlMapProvider,
|
private val urlMapProvider: UrlMapProvider,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO inject this properly?
|
// TODO inject this properly?
|
||||||
|
@ -166,7 +178,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
|
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
|
||||||
}
|
}
|
||||||
if (messageContent.relatesTo?.type == RelationType.REPLACE ||
|
if (messageContent.relatesTo?.type == RelationType.REPLACE ||
|
||||||
event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||||
) {
|
) {
|
||||||
// This is an edit event, we should display it when debugging as a notice event
|
// This is an edit event, we should display it when debugging as a notice event
|
||||||
return noticeItemFactory.create(params)
|
return noticeItemFactory.create(params)
|
||||||
|
@ -180,16 +192,16 @@ class MessageItemFactory @Inject constructor(
|
||||||
// always hide summary when we are on thread timeline
|
// always hide summary when we are on thread timeline
|
||||||
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
|
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
|
||||||
|
|
||||||
// val all = event.root.toContent()
|
// val all = event.root.toContent()
|
||||||
// val ev = all.toModel<Event>()
|
// val ev = all.toModel<Event>()
|
||||||
val messageItem = when (messageContent) {
|
val messageItem = when (messageContent) {
|
||||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
||||||
is MessageAudioContent -> {
|
is MessageAudioContent -> {
|
||||||
if (messageContent.voiceMessageIndicator != null) {
|
if (messageContent.voiceMessageIndicator != null) {
|
||||||
buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
|
buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
|
||||||
} else {
|
} else {
|
||||||
|
@ -197,25 +209,27 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageLocationContent -> {
|
is MessageLocationContent -> {
|
||||||
if (vectorPreferences.labsRenderLocationsInTimeline()) {
|
if (vectorPreferences.labsRenderLocationsInTimeline()) {
|
||||||
buildLocationItem(messageContent, informationData, highlight, attributes)
|
buildLocationItem(messageContent, informationData, highlight, attributes)
|
||||||
} else {
|
} else {
|
||||||
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
return messageItem?.apply {
|
return messageItem?.apply {
|
||||||
layout(informationData.messageLayout.layoutRes)
|
layout(informationData.messageLayout.layoutRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildLocationItem(locationContent: MessageLocationContent,
|
private fun buildLocationItem(
|
||||||
informationData: MessageInformationData,
|
locationContent: MessageLocationContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
attributes: AbsMessageItem.Attributes): MessageLocationItem? {
|
highlight: Boolean,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageLocationItem? {
|
||||||
val width = timelineMediaSizeProvider.getMaxSize().first
|
val width = timelineMediaSizeProvider.getMaxSize().first
|
||||||
val height = dimensionConverter.dpToPx(200)
|
val height = dimensionConverter.dpToPx(200)
|
||||||
|
|
||||||
|
@ -226,98 +240,110 @@ class MessageItemFactory @Inject constructor(
|
||||||
val userId = if (locationContent.isSelfLocation()) informationData.senderId else null
|
val userId = if (locationContent.isSelfLocation()) informationData.senderId else null
|
||||||
|
|
||||||
return MessageLocationItem_()
|
return MessageLocationItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.locationUrl(locationUrl)
|
.locationUrl(locationUrl)
|
||||||
.mapWidth(width)
|
.mapWidth(width)
|
||||||
.mapHeight(height)
|
.mapHeight(height)
|
||||||
.userId(userId)
|
.userId(userId)
|
||||||
.locationPinProvider(locationPinProvider)
|
.locationPinProvider(locationPinProvider)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPollItem(pollContent: MessagePollContent,
|
private fun buildPollItem(
|
||||||
informationData: MessageInformationData,
|
pollContent: MessagePollContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
val optionViewStates = mutableListOf<PollOptionViewState>()
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): PollItem {
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||||
val isEnded = pollResponseSummary?.isClosed.orFalse()
|
val pollState = createPollState(informationData, pollResponseSummary, pollContent)
|
||||||
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
||||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
val isPollSent = informationData.sendState.isSent()
|
val question = createPollQuestion(informationData, questionText, callback)
|
||||||
val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE
|
val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
|
||||||
|
val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
|
||||||
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
|
|
||||||
when {
|
|
||||||
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
|
|
||||||
isPollUndisclosed -> ""
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pollContent.getBestPollCreationInfo()?.answers?.forEach { option ->
|
|
||||||
val voteSummary = pollResponseSummary?.votes?.get(option.id)
|
|
||||||
val isMyVote = pollResponseSummary?.myVote == option.id
|
|
||||||
val voteCount = voteSummary?.total ?: 0
|
|
||||||
val votePercentage = voteSummary?.percentage ?: 0.0
|
|
||||||
val optionId = option.id ?: ""
|
|
||||||
val optionAnswer = option.getBestAnswer() ?: ""
|
|
||||||
|
|
||||||
optionViewStates.add(
|
|
||||||
if (!isPollSent) {
|
|
||||||
// Poll event is not send yet. Disable option.
|
|
||||||
PollOptionViewState.PollSending(optionId, optionAnswer)
|
|
||||||
} else if (isEnded) {
|
|
||||||
// Poll is ended. Disable option, show votes and mark the winner.
|
|
||||||
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
|
||||||
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) {
|
|
||||||
// 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)
|
|
||||||
} else {
|
|
||||||
// User didn't voted yet and poll is not ended yet. Enable options, hide votes.
|
|
||||||
PollOptionViewState.PollReady(optionId, optionAnswer)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val question = pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
|
|
||||||
|
|
||||||
return PollItem_()
|
return PollItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.eventId(informationData.eventId)
|
.eventId(informationData.eventId)
|
||||||
.pollQuestion(
|
.pollQuestion(question)
|
||||||
if (informationData.hasBeenEdited) {
|
.canVote(pollState.isVotable())
|
||||||
annotateWithEdited(question, callback, informationData)
|
.totalVotesText(totalVotesText)
|
||||||
} else {
|
.optionViewStates(optionViewStates)
|
||||||
question
|
.edited(informationData.hasBeenEdited)
|
||||||
}.toEpoxyCharSequence()
|
.highlighted(highlight)
|
||||||
)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.pollSent(isPollSent)
|
.callback(callback)
|
||||||
.totalVotesText(totalVotesText)
|
|
||||||
.optionViewStates(optionViewStates)
|
|
||||||
.edited(informationData.hasBeenEdited)
|
|
||||||
.highlighted(highlight)
|
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
|
||||||
.callback(callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAudioMessageItem(messageContent: MessageAudioContent,
|
private fun createPollState(
|
||||||
@Suppress("UNUSED_PARAMETER")
|
informationData: MessageInformationData,
|
||||||
informationData: MessageInformationData,
|
pollResponseSummary: PollResponseData?,
|
||||||
highlight: Boolean,
|
pollContent: MessagePollContent,
|
||||||
attributes: AbsMessageItem.Attributes): MessageFileItem? {
|
): PollState = when {
|
||||||
|
!informationData.sendState.isSent() -> Sending
|
||||||
|
pollResponseSummary?.isClosed.orFalse() -> Ended
|
||||||
|
pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed
|
||||||
|
pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0)
|
||||||
|
else -> Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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) {
|
||||||
|
Sending -> PollSending(optionId, optionAnswer)
|
||||||
|
Ready -> PollReady(optionId, optionAnswer)
|
||||||
|
is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
||||||
|
Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote)
|
||||||
|
Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPollQuestion(
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
question: String,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
) = if (informationData.hasBeenEdited) {
|
||||||
|
annotateWithEdited(question, callback, informationData)
|
||||||
|
} else {
|
||||||
|
question
|
||||||
|
}.toEpoxyCharSequence()
|
||||||
|
|
||||||
|
private fun createTotalVotesText(
|
||||||
|
pollState: PollState,
|
||||||
|
pollResponseSummary: PollResponseData?,
|
||||||
|
): String {
|
||||||
|
val votes = pollResponseSummary?.totalVotes ?: 0
|
||||||
|
return when {
|
||||||
|
pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
|
||||||
|
pollState is Undisclosed -> ""
|
||||||
|
pollState is 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildAudioMessageItem(
|
||||||
|
messageContent: MessageAudioContent,
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
highlight: Boolean,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageFileItem? {
|
||||||
val fileUrl = messageContent.getFileUrl()?.let {
|
val fileUrl = messageContent.getFileUrl()?.let {
|
||||||
if (informationData.sentByMe && !informationData.sendState.isSent()) {
|
if (informationData.sentByMe && !informationData.sendState.isSent()) {
|
||||||
it
|
it
|
||||||
|
@ -326,29 +352,31 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
} ?: ""
|
} ?: ""
|
||||||
return MessageFileItem_()
|
return MessageFileItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.izLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
.izLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
||||||
.izDownloaded(session.fileService().isFileInCache(
|
.izDownloaded(session.fileService().isFileInCache(
|
||||||
fileUrl,
|
fileUrl,
|
||||||
messageContent.getFileName(),
|
messageContent.getFileName(),
|
||||||
messageContent.mimeType,
|
messageContent.mimeType,
|
||||||
messageContent.encryptedFileInfo?.toElementToDecrypt())
|
messageContent.encryptedFileInfo?.toElementToDecrypt())
|
||||||
)
|
)
|
||||||
.mxcUrl(fileUrl)
|
.mxcUrl(fileUrl)
|
||||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.iconRes(R.drawable.ic_headphones)
|
.iconRes(R.drawable.ic_headphones)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVoiceMessageItem(params: TimelineItemFactoryParams,
|
private fun buildVoiceMessageItem(
|
||||||
messageContent: MessageAudioContent,
|
params: TimelineItemFactoryParams,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
messageContent: MessageAudioContent,
|
||||||
informationData: MessageInformationData,
|
@Suppress("UNUSED_PARAMETER")
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
attributes: AbsMessageItem.Attributes): MessageVoiceItem? {
|
highlight: Boolean,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageVoiceItem? {
|
||||||
val fileUrl = messageContent.getFileUrl()?.let {
|
val fileUrl = messageContent.getFileUrl()?.let {
|
||||||
if (informationData.sentByMe && !informationData.sendState.isSent()) {
|
if (informationData.sentByMe && !informationData.sendState.isSent()) {
|
||||||
it
|
it
|
||||||
|
@ -376,32 +404,34 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageVoiceItem_()
|
return MessageVoiceItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.duration(messageContent.audioWaveformInfo?.duration ?: 0)
|
.duration(messageContent.audioWaveformInfo?.duration ?: 0)
|
||||||
.waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
|
.waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
|
||||||
.playbackControlButtonClickListener(playbackControlButtonClickListener)
|
.playbackControlButtonClickListener(playbackControlButtonClickListener)
|
||||||
.waveformTouchListener(waveformTouchListener)
|
.waveformTouchListener(waveformTouchListener)
|
||||||
.voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
|
.voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
|
||||||
.izLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
.izLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
||||||
.izDownloaded(session.fileService().isFileInCache(
|
.izDownloaded(session.fileService().isFileInCache(
|
||||||
fileUrl,
|
fileUrl,
|
||||||
messageContent.getFileName(),
|
messageContent.getFileName(),
|
||||||
messageContent.mimeType,
|
messageContent.mimeType,
|
||||||
messageContent.encryptedFileInfo?.toElementToDecrypt())
|
messageContent.encryptedFileInfo?.toElementToDecrypt())
|
||||||
)
|
)
|
||||||
.mxcUrl(fileUrl)
|
.mxcUrl(fileUrl)
|
||||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
|
private fun buildVerificationRequestMessageItem(
|
||||||
@Suppress("UNUSED_PARAMETER")
|
messageContent: MessageVerificationRequestContent,
|
||||||
informationData: MessageInformationData,
|
@Suppress("UNUSED_PARAMETER")
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): VerificationRequestItem? {
|
||||||
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
||||||
val myUserId = session.myUserId
|
val myUserId = session.myUserId
|
||||||
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
||||||
|
@ -415,140 +445,148 @@ class MessageItemFactory @Inject constructor(
|
||||||
informationData.memberName
|
informationData.memberName
|
||||||
}
|
}
|
||||||
return VerificationRequestItem_()
|
return VerificationRequestItem_()
|
||||||
.attributes(
|
.attributes(
|
||||||
VerificationRequestItem.Attributes(
|
VerificationRequestItem.Attributes(
|
||||||
otherUserId = otherUserId,
|
otherUserId = otherUserId,
|
||||||
otherUserName = otherUserName.toString(),
|
otherUserName = otherUserName.toString(),
|
||||||
referenceId = informationData.eventId,
|
referenceId = informationData.eventId,
|
||||||
informationData = informationData,
|
informationData = informationData,
|
||||||
avatarRenderer = attributes.avatarRenderer,
|
avatarRenderer = attributes.avatarRenderer,
|
||||||
messageColorProvider = attributes.messageColorProvider,
|
messageColorProvider = attributes.messageColorProvider,
|
||||||
itemLongClickListener = attributes.itemLongClickListener,
|
itemLongClickListener = attributes.itemLongClickListener,
|
||||||
itemClickListener = attributes.itemClickListener,
|
itemClickListener = attributes.itemClickListener,
|
||||||
reactionPillCallback = attributes.reactionPillCallback,
|
reactionPillCallback = attributes.reactionPillCallback,
|
||||||
readReceiptsCallback = attributes.readReceiptsCallback,
|
readReceiptsCallback = attributes.readReceiptsCallback,
|
||||||
emojiTypeFace = attributes.emojiTypeFace,
|
emojiTypeFace = attributes.emojiTypeFace,
|
||||||
reactionsSummaryEvents = attributes.reactionsSummaryEvents
|
reactionsSummaryEvents = attributes.reactionsSummaryEvents,
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.callback(callback)
|
)
|
||||||
.highlighted(highlight)
|
.callback(callback)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.highlighted(highlight)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildFileMessageItem(messageContent: MessageFileContent,
|
private fun buildFileMessageItem(
|
||||||
// informationData: MessageInformationData,
|
messageContent: MessageFileContent,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
// callback: TimelineEventController.Callback?,
|
attributes: AbsMessageItem.Attributes,
|
||||||
attributes: AbsMessageItem.Attributes): MessageFileItem? {
|
): MessageFileItem? {
|
||||||
val mxcUrl = messageContent.getFileUrl() ?: ""
|
val mxcUrl = messageContent.getFileUrl() ?: ""
|
||||||
return MessageFileItem_()
|
return MessageFileItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
|
.izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
|
||||||
.izDownloaded(session.fileService().isFileInCache(messageContent))
|
.izDownloaded(session.fileService().isFileInCache(messageContent))
|
||||||
.mxcUrl(mxcUrl)
|
.mxcUrl(mxcUrl)
|
||||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.filename(messageContent.body)
|
.filename(messageContent.body)
|
||||||
.iconRes(R.drawable.ic_paperclip)
|
.iconRes(R.drawable.ic_paperclip)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
private fun buildNotHandledMessageItem(
|
||||||
informationData: MessageInformationData,
|
messageContent: MessageContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
// For compatibility reason we should display the body
|
// For compatibility reason we should display the body
|
||||||
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
private fun buildImageMessageItem(
|
||||||
@Suppress("UNUSED_PARAMETER")
|
messageContent: MessageImageInfoContent,
|
||||||
informationData: MessageInformationData,
|
@Suppress("UNUSED_PARAMETER")
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageImageVideoItem? {
|
||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val data = ImageContentRenderer.Data(
|
val data = ImageContentRenderer.Data(
|
||||||
eventId = informationData.eventId,
|
eventId = informationData.eventId,
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
mimeType = messageContent.mimeType,
|
mimeType = messageContent.mimeType,
|
||||||
url = messageContent.getFileUrl(),
|
url = messageContent.getFileUrl(),
|
||||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
height = messageContent.info?.height,
|
height = messageContent.info?.height,
|
||||||
maxHeight = maxHeight,
|
maxHeight = maxHeight,
|
||||||
width = messageContent.info?.width,
|
width = messageContent.info?.width,
|
||||||
maxWidth = maxWidth,
|
maxWidth = maxWidth,
|
||||||
allowNonMxcUrls = informationData.sendState.isSending()
|
allowNonMxcUrls = informationData.sendState.isSending()
|
||||||
)
|
)
|
||||||
return MessageImageVideoItem_()
|
return MessageImageVideoItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
.playable(messageContent.mimeType == MimeTypes.Gif)
|
.playable(messageContent.mimeType == MimeTypes.Gif)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.mediaData(data)
|
.mediaData(data)
|
||||||
.apply {
|
.apply {
|
||||||
if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
|
if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
|
||||||
mode(ImageContentRenderer.Mode.STICKER)
|
mode(ImageContentRenderer.Mode.STICKER)
|
||||||
clickListener { view ->
|
clickListener { view ->
|
||||||
callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
|
callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clickListener { view ->
|
clickListener { view ->
|
||||||
callback?.onImageMessageClicked(messageContent, data, view, emptyList())
|
callback?.onImageMessageClicked(messageContent, data, view, emptyList())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
private fun buildVideoMessageItem(
|
||||||
informationData: MessageInformationData,
|
messageContent: MessageVideoContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageImageVideoItem? {
|
||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val thumbnailData = ImageContentRenderer.Data(
|
val thumbnailData = ImageContentRenderer.Data(
|
||||||
eventId = informationData.eventId,
|
eventId = informationData.eventId,
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
mimeType = messageContent.mimeType,
|
mimeType = messageContent.mimeType,
|
||||||
url = messageContent.videoInfo?.getThumbnailUrl(),
|
url = messageContent.videoInfo?.getThumbnailUrl(),
|
||||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||||
height = messageContent.videoInfo?.height,
|
height = messageContent.videoInfo?.height,
|
||||||
maxHeight = maxHeight,
|
maxHeight = maxHeight,
|
||||||
width = messageContent.videoInfo?.width,
|
width = messageContent.videoInfo?.width,
|
||||||
maxWidth = maxWidth,
|
maxWidth = maxWidth,
|
||||||
allowNonMxcUrls = informationData.sendState.isSending()
|
allowNonMxcUrls = informationData.sendState.isSending()
|
||||||
)
|
)
|
||||||
|
|
||||||
val videoData = VideoContentRenderer.Data(
|
val videoData = VideoContentRenderer.Data(
|
||||||
eventId = informationData.eventId,
|
eventId = informationData.eventId,
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
mimeType = messageContent.mimeType,
|
mimeType = messageContent.mimeType,
|
||||||
url = messageContent.getFileUrl(),
|
url = messageContent.getFileUrl(),
|
||||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
thumbnailMediaData = thumbnailData
|
thumbnailMediaData = thumbnailData
|
||||||
)
|
)
|
||||||
|
|
||||||
return MessageImageVideoItem_()
|
return MessageImageVideoItem_()
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||||
.playable(true)
|
.playable(true)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.mediaData(thumbnailData)
|
.mediaData(thumbnailData)
|
||||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
private fun buildItemForTextContent(
|
||||||
informationData: MessageInformationData,
|
messageContent: MessageTextContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): VectorEpoxyModel<*>? {
|
||||||
val matrixFormattedBody = messageContent.matrixFormattedBody
|
val matrixFormattedBody = messageContent.matrixFormattedBody
|
||||||
return if (matrixFormattedBody != null) {
|
return if (matrixFormattedBody != null) {
|
||||||
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
|
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
|
||||||
|
@ -557,50 +595,56 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildFormattedTextItem(matrixFormattedBody: String,
|
private fun buildFormattedTextItem(
|
||||||
informationData: MessageInformationData,
|
matrixFormattedBody: String,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val compressed = htmlCompressor.compress(matrixFormattedBody)
|
val compressed = htmlCompressor.compress(matrixFormattedBody)
|
||||||
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
|
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
|
||||||
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
|
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMessageTextItem(body: CharSequence,
|
private fun buildMessageTextItem(
|
||||||
isFormatted: Boolean,
|
body: CharSequence,
|
||||||
informationData: MessageInformationData,
|
isFormatted: Boolean,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val renderedBody = textRenderer.render(body)
|
val renderedBody = textRenderer.render(body)
|
||||||
val bindingOptions = spanUtils.getBindingOptions(renderedBody)
|
val bindingOptions = spanUtils.getBindingOptions(renderedBody)
|
||||||
val linkifiedBody = renderedBody.linkify(callback)
|
val linkifiedBody = renderedBody.linkify(callback)
|
||||||
|
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(
|
.message(
|
||||||
if (informationData.hasBeenEdited) {
|
if (informationData.hasBeenEdited) {
|
||||||
annotateWithEdited(linkifiedBody, callback, informationData)
|
annotateWithEdited(linkifiedBody, callback, informationData)
|
||||||
} else {
|
} else {
|
||||||
linkifiedBody
|
linkifiedBody
|
||||||
}.toEpoxyCharSequence()
|
}.toEpoxyCharSequence()
|
||||||
)
|
)
|
||||||
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
||||||
.bindingOptions(bindingOptions)
|
.bindingOptions(bindingOptions)
|
||||||
.markwonPlugins(htmlRenderer.get().plugins)
|
.markwonPlugins(htmlRenderer.get().plugins)
|
||||||
.searchForPills(isFormatted)
|
.searchForPills(isFormatted)
|
||||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
.previewUrlCallback(callback)
|
.previewUrlCallback(callback)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.movementMethod(createLinkMovementMethod(callback))
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun annotateWithEdited(linkifiedBody: CharSequence,
|
private fun annotateWithEdited(
|
||||||
callback: TimelineEventController.Callback?,
|
linkifiedBody: CharSequence,
|
||||||
informationData: MessageInformationData): Spannable {
|
callback: TimelineEventController.Callback?,
|
||||||
|
informationData: MessageInformationData,
|
||||||
|
): Spannable {
|
||||||
val spannable = SpannableStringBuilder()
|
val spannable = SpannableStringBuilder()
|
||||||
spannable.append(linkifiedBody)
|
spannable.append(linkifiedBody)
|
||||||
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
||||||
|
@ -609,17 +653,17 @@ class MessageItemFactory @Inject constructor(
|
||||||
val editStart = spannable.lastIndexOf(editedSuffix)
|
val editStart = spannable.lastIndexOf(editedSuffix)
|
||||||
val editEnd = editStart + editedSuffix.length
|
val editEnd = editStart + editedSuffix.length
|
||||||
spannable.setSpan(
|
spannable.setSpan(
|
||||||
ForegroundColorSpan(color),
|
ForegroundColorSpan(color),
|
||||||
editStart,
|
editStart,
|
||||||
editEnd,
|
editEnd,
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
|
|
||||||
// Note: text size is set to 14sp
|
// Note: text size is set to 14sp
|
||||||
spannable.setSpan(
|
spannable.setSpan(
|
||||||
AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
|
AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
|
||||||
editStart,
|
editStart,
|
||||||
editEnd,
|
editEnd,
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
|
|
||||||
spannable.setSpan(object : ClickableSpan() {
|
spannable.setSpan(object : ClickableSpan() {
|
||||||
override fun onClick(widget: View) {
|
override fun onClick(widget: View) {
|
||||||
|
@ -630,18 +674,20 @@ class MessageItemFactory @Inject constructor(
|
||||||
// nop
|
// nop
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editStart,
|
editStart,
|
||||||
editEnd,
|
editEnd,
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNoticeMessageItem(messageContent: MessageNoticeContent,
|
private fun buildNoticeMessageItem(
|
||||||
@Suppress("UNUSED_PARAMETER")
|
messageContent: MessageNoticeContent,
|
||||||
informationData: MessageInformationData,
|
@Suppress("UNUSED_PARAMETER")
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val htmlBody = messageContent.getHtmlBody()
|
val htmlBody = messageContent.getHtmlBody()
|
||||||
val formattedBody = span {
|
val formattedBody = span {
|
||||||
text = htmlBody
|
text = htmlBody
|
||||||
|
@ -653,22 +699,24 @@ class MessageItemFactory @Inject constructor(
|
||||||
val message = formattedBody.linkify(callback)
|
val message = formattedBody.linkify(callback)
|
||||||
|
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
.previewUrlCallback(callback)
|
.previewUrlCallback(callback)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.message(message.toEpoxyCharSequence())
|
.message(message.toEpoxyCharSequence())
|
||||||
.bindingOptions(bindingOptions)
|
.bindingOptions(bindingOptions)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.movementMethod(createLinkMovementMethod(callback))
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
|
private fun buildEmoteMessageItem(
|
||||||
informationData: MessageInformationData,
|
messageContent: MessageEmoteContent,
|
||||||
highlight: Boolean,
|
informationData: MessageInformationData,
|
||||||
callback: TimelineEventController.Callback?,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val formattedBody = SpannableStringBuilder()
|
val formattedBody = SpannableStringBuilder()
|
||||||
formattedBody.append("* ${informationData.memberName} ")
|
formattedBody.append("* ${informationData.memberName} ")
|
||||||
formattedBody.append(messageContent.getHtmlBody())
|
formattedBody.append(messageContent.getHtmlBody())
|
||||||
|
@ -676,46 +724,48 @@ class MessageItemFactory @Inject constructor(
|
||||||
val message = formattedBody.linkify(callback)
|
val message = formattedBody.linkify(callback)
|
||||||
|
|
||||||
return MessageTextItem_()
|
return MessageTextItem_()
|
||||||
.message(
|
.message(
|
||||||
if (informationData.hasBeenEdited) {
|
if (informationData.hasBeenEdited) {
|
||||||
annotateWithEdited(message, callback, informationData)
|
annotateWithEdited(message, callback, informationData)
|
||||||
} else {
|
} else {
|
||||||
message
|
message
|
||||||
}.toEpoxyCharSequence()
|
}.toEpoxyCharSequence()
|
||||||
)
|
)
|
||||||
.bindingOptions(bindingOptions)
|
.bindingOptions(bindingOptions)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||||
.imageContentRenderer(imageContentRenderer)
|
.imageContentRenderer(imageContentRenderer)
|
||||||
.previewUrlCallback(callback)
|
.previewUrlCallback(callback)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.movementMethod(createLinkMovementMethod(callback))
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
||||||
return matrixFormattedBody
|
return matrixFormattedBody
|
||||||
?.let { htmlCompressor.compress(it) }
|
?.let { htmlCompressor.compress(it) }
|
||||||
?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
|
?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
|
||||||
?: body
|
?: body
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
|
private fun buildRedactedItem(
|
||||||
highlight: Boolean): RedactedMessageItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
highlight: Boolean,
|
||||||
|
): RedactedMessageItem? {
|
||||||
return RedactedMessageItem_()
|
return RedactedMessageItem_()
|
||||||
.layout(attributes.informationData.messageLayout.layoutRes)
|
.layout(attributes.informationData.messageLayout.layoutRes)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Int?>?.toFft(): List<Int>? {
|
private fun List<Int?>?.toFft(): List<Int>? {
|
||||||
return this
|
return this
|
||||||
?.filterNotNull()
|
?.filterNotNull()
|
||||||
?.map {
|
?.map {
|
||||||
// Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
|
// Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
|
||||||
it * AudioWaveformView.MAX_FFT / 1024
|
it * AudioWaveformView.MAX_FFT / 1024
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -39,13 +39,13 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
var eventId: String? = null
|
var eventId: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var pollSent: Boolean = false
|
var canVote: Boolean = false
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var totalVotesText: String? = null
|
var totalVotesText: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var edited: Boolean = false
|
var edited: Boolean = false
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var optionViewStates: List<PollOptionViewState>
|
lateinit var optionViewStates: List<PollOptionViewState>
|
||||||
|
@ -54,7 +54,6 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
val relatedEventId = eventId ?: return
|
|
||||||
|
|
||||||
renderSendState(holder.view, holder.questionTextView)
|
renderSendState(holder.view, holder.questionTextView)
|
||||||
|
|
||||||
|
@ -73,13 +72,19 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
optionViewStates.forEachIndexed { index, optionViewState ->
|
optionViewStates.forEachIndexed { index, optionViewState ->
|
||||||
views.getOrNull(index)?.let {
|
views.getOrNull(index)?.let {
|
||||||
it.render(optionViewState)
|
it.render(optionViewState)
|
||||||
it.setOnClickListener {
|
it.setOnClickListener { onPollItemClick(optionViewState) }
|
||||||
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onPollItemClick(optionViewState: PollOptionViewState) {
|
||||||
|
val relatedEventId = eventId
|
||||||
|
|
||||||
|
if (canVote && relatedEventId != null) {
|
||||||
|
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val questionTextView by bind<TextView>(R.id.questionTextView)
|
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||||
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
||||||
|
|
|
@ -75,9 +75,9 @@ import im.vector.app.features.onboarding.OnboardingActivity
|
||||||
import im.vector.app.features.pin.PinActivity
|
import im.vector.app.features.pin.PinActivity
|
||||||
import im.vector.app.features.pin.PinArgs
|
import im.vector.app.features.pin.PinArgs
|
||||||
import im.vector.app.features.pin.PinMode
|
import im.vector.app.features.pin.PinMode
|
||||||
|
import im.vector.app.features.poll.PollMode
|
||||||
import im.vector.app.features.poll.create.CreatePollActivity
|
import im.vector.app.features.poll.create.CreatePollActivity
|
||||||
import im.vector.app.features.poll.create.CreatePollArgs
|
import im.vector.app.features.poll.create.CreatePollArgs
|
||||||
import im.vector.app.features.poll.create.PollMode
|
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.location.LocationSharingMode
|
||||||
import im.vector.app.features.login.LoginConfig
|
import im.vector.app.features.login.LoginConfig
|
||||||
import im.vector.app.features.media.AttachmentData
|
import im.vector.app.features.media.AttachmentData
|
||||||
import im.vector.app.features.pin.PinMode
|
import im.vector.app.features.pin.PinMode
|
||||||
import im.vector.app.features.poll.create.PollMode
|
import im.vector.app.features.poll.PollMode
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.poll.create
|
package im.vector.app.features.poll
|
||||||
|
|
||||||
enum class PollMode {
|
enum class PollMode {
|
||||||
CREATE,
|
CREATE,
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
sealed interface PollState {
|
||||||
|
object Sending : PollState
|
||||||
|
object Ready : PollState
|
||||||
|
data class Voted(val votes: Int) : PollState
|
||||||
|
object Undisclosed : PollState
|
||||||
|
object Ended : PollState
|
||||||
|
|
||||||
|
fun isVotable() = this !is Sending && this !is Ended
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
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.PollMode
|
||||||
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 org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.poll.PollMode
|
||||||
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.model.message.PollType
|
||||||
|
|
|
@ -17,17 +17,18 @@
|
||||||
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 im.vector.app.features.poll.PollMode
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
data class CreatePollViewState(
|
data class CreatePollViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val editedEventId: String?,
|
val editedEventId: String?,
|
||||||
val mode: PollMode,
|
val mode: PollMode,
|
||||||
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_UNSTABLE
|
val pollType: PollType = PollType.DISCLOSED_UNSTABLE
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: CreatePollArgs) : this(
|
constructor(args: CreatePollArgs) : this(
|
||||||
|
|
Loading…
Reference in a new issue