From f6cabfffd9832a4045bfd64651a2895cbd650451 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 23 Dec 2022 16:24:10 +0300 Subject: [PATCH 01/15] Set poll end event type as displayable. --- .../room/detail/timeline/helper/TimelineDisplayableEvents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 51e961f247..2dcb6cc6d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -55,6 +55,7 @@ object TimelineDisplayableEvents { VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START.values + + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values + EventType.BEACON_LOCATION_DATA.values } From 486968fdc2bfb0abf7661ecc1cd45404684c55fd Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 26 Dec 2022 14:41:38 +0300 Subject: [PATCH 02/15] Render ended poll. --- .../model/message/MessageEndPollContent.kt | 12 +++++-- .../session/room/model/message/MessageType.kt | 1 + .../session/room/timeline/TimelineEvent.kt | 2 ++ .../timeline/factory/MessageItemFactory.kt | 29 ++++++++++++++++ .../timeline/factory/TimelineItemFactory.kt | 4 +-- .../helper/MessageInformationDataFactory.kt | 33 +++++++++++-------- 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt index f0511903d0..6e31320b13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent /** @@ -25,5 +26,12 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon */ @JsonClass(generateAdapter = true) data class MessageEndPollContent( - @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null -) + /** + * Local message type, not from server. + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_POLL_END, + @Json(name = "body") override val body: String = "", + @Json(name = "m.new_content") override val newContent: Content? = null, + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null +) : MessageContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index e97a5be303..f6b7675d4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -36,6 +36,7 @@ object MessageType { // Because poll events are not message events and they don't have msgtype field const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start" const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response" + const val MSGTYPE_POLL_END = "org.matrix.android.sdk.poll.end" const val MSGTYPE_CONFETTI = "nic.custom.confetti" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 9053425a39..6320ea964d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoCo import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -148,6 +149,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { // so toModel won't parse them correctly // It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion? in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() + in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel() else -> (getLastEditNewContent() ?: root.getClearContent()).toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 42e031a3c4..b4ba146176 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -91,11 +91,13 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent @@ -203,6 +205,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes) @@ -261,6 +264,32 @@ class MessageItemFactory @Inject constructor( .callback(callback) } + + + private fun buildEndedPollItem( + endedPollContent: MessageEndPollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): PollItem? { + val pollStartEventId = endedPollContent.relatesTo?.eventId ?: return null + val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) + val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null + + val aggregatedInformationData = informationData.copy( + pollResponseAggregatedSummary = messageInformationDataFactory.mapPollResponseSummary(pollStartEvent.annotations?.pollResponseSummary) + ) + + return buildPollItem( + pollContent, + aggregatedInformationData, + highlight, + callback, + attributes + ) + } + private fun createPollQuestion( informationData: MessageInformationData, question: String, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index ae3ea143a7..61b2385d1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -102,6 +102,7 @@ class TimelineItemFactory @Inject constructor( // Message itemsX EventType.STICKER, in EventType.POLL_START.values, + in EventType.POLL_END.values, EventType.MESSAGE -> messageItemFactory.create(params) EventType.REDACTION, EventType.KEY_VERIFICATION_ACCEPT, @@ -114,8 +115,7 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - in EventType.POLL_RESPONSE.values, - in EventType.POLL_END.values -> noticeItemFactory.create(params) + in EventType.POLL_RESPONSE.values -> noticeItemFactory.create(params) in EventType.BEACON_LOCATION_DATA.values -> { if (event.root.isRedacted()) { messageItemFactory.create(params) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 57a4388f74..d356c8a7d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -99,20 +100,7 @@ class MessageInformationDataFactory @Inject constructor( memberName = event.senderInfo.disambiguatedDisplayName, messageLayout = messageLayout, reactionsSummary = reactionsSummaryFactory.create(event), - pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let { - PollResponseData( - myVote = it.aggregatedContent?.myVote, - isClosed = it.closedTime != null, - votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> - PollVoteSummaryData( - total = votesSummary.value.total, - percentage = votesSummary.value.percentage - ) - }, - winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 - ) - }, + pollResponseAggregatedSummary = mapPollResponseSummary(event.annotations?.pollResponseSummary), hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> @@ -133,6 +121,23 @@ class MessageInformationDataFactory @Inject constructor( ) } + fun mapPollResponseSummary(pollResponseSummary: PollResponseAggregatedSummary?): PollResponseData? { + return pollResponseSummary?.let { + PollResponseData( + myVote = it.aggregatedContent?.myVote, + isClosed = it.closedTime != null, + votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> + PollVoteSummaryData( + total = votesSummary.value.total, + percentage = votesSummary.value.percentage + ) + }, + winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, + totalVotes = it.aggregatedContent?.totalVotes ?: 0 + ) + } + } + private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) { event.root.toValidDecryptedEvent()?.let { session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId From 374445eed6001cc2ddc2a940b7c516e57f18766a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 26 Dec 2022 17:57:04 +0300 Subject: [PATCH 03/15] Update poll layout. --- .../detail/timeline/item/PollOptionView.kt | 11 ++++---- .../src/main/res/layout/item_poll_option.xml | 27 ++++++------------- .../res/layout/item_timeline_event_poll.xml | 1 - 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt index 20aa6e3af2..38e6c5e6d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt @@ -53,35 +53,36 @@ class PollOptionView @JvmOverloads constructor( private fun renderPollSending() { views.optionCheckImageView.isVisible = false - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(false) } private fun renderPollEnded(state: PollOptionViewState.PollEnded) { views.optionCheckImageView.isVisible = false - views.optionWinnerImageView.isVisible = state.isWinner + val drawableStart = if (state.isWinner) R.drawable.ic_poll_winner else 0 + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, 0, 0, 0) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isWinner) } private fun renderPollReady() { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(false) } private fun renderPollVoted(state: PollOptionViewState.PollVoted) { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isSelected) } private fun renderPollUndisclosed(state: PollOptionViewState.PollUndisclosed) { views.optionCheckImageView.isVisible = true - views.optionWinnerImageView.isVisible = false + views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) hideVotes() renderVoteSelection(state.isSelected) } diff --git a/vector/src/main/res/layout/item_poll_option.xml b/vector/src/main/res/layout/item_poll_option.xml index 986bfeaa35..ff7d4498fb 100644 --- a/vector/src/main/res/layout/item_poll_option.xml +++ b/vector/src/main/res/layout/item_poll_option.xml @@ -36,34 +36,23 @@ android:layout_marginStart="12dp" android:layout_marginTop="16dp" android:layout_marginEnd="12dp" - app:layout_constraintEnd_toEndOf="@id/optionWinnerImageView" + app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView" app:layout_constraintStart_toEndOf="@id/optionCheckImageView" app:layout_constraintTop_toTopOf="parent" tools:text="@sample/poll.json/data/answer" /> - - @@ -78,9 +67,9 @@ android:layout_marginBottom="8dp" android:progressDrawable="@drawable/poll_option_progressbar_checked" app:layout_constraintBottom_toBottomOf="@id/optionBorderImageView" - app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/optionNameTextView" tools:progress="60" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml index 393b736260..a3fa07ade2 100644 --- a/vector/src/main/res/layout/item_timeline_event_poll.xml +++ b/vector/src/main/res/layout/item_timeline_event_poll.xml @@ -13,7 +13,6 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textColor="?vctr_content_primary" - android:textStyle="bold" app:layout_constraintHorizontal_bias="0" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From b53615a8d7f827b026b481a7f4a2cdf2abb35837 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 14:36:54 +0300 Subject: [PATCH 04/15] Add reply action for poll end events. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../im/vector/app/core/extensions/TimelineEvent.kt | 2 +- .../timeline/action/CheckIfCanReplyEventUseCase.kt | 11 +++++++++-- .../detail/timeline/action/MessageActionsViewModel.kt | 5 +++-- .../render/ProcessBodyOfReplyToEventUseCase.kt | 6 +++++- .../action/CheckIfCanReplyEventUseCaseTest.kt | 1 + 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..2b8e397521 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3501,4 +3501,5 @@ sent a video. sent a sticker. created a poll. + ended a poll. diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index c94f9cd921..89bd28fc93 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent fun TimelineEvent.canReact(): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values && + return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values && root.sendState == SendState.SYNCED && !root.isRedacted() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt index a9df059cc1..fdd94d1559 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt @@ -25,8 +25,14 @@ import javax.inject.Inject class CheckIfCanReplyEventUseCase @Inject constructor() { fun execute(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { - // Only EventType.MESSAGE, EventType.POLL_START and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment - if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE) return false + // Only EventType.MESSAGE, EventType.POLL_START, EventType.POLL_END and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment + if (event.root.getClearType() !in + EventType.STATE_ROOM_BEACON_INFO.values + + EventType.POLL_START.values + + EventType.POLL_END.values + + EventType.MESSAGE + ) return false + if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { MessageType.MSGTYPE_TEXT, @@ -37,6 +43,7 @@ class CheckIfCanReplyEventUseCase @Inject constructor() { MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_BEACON_INFO, MessageType.MSGTYPE_LOCATION -> true else -> false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index a6d7e8386f..646cfa50d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -498,6 +498,7 @@ class MessageActionsViewModel @AssistedInject constructor( MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_STICKER_LOCAL -> event.root.threadDetails?.isRootThread ?: false else -> false } @@ -529,8 +530,8 @@ class MessageActionsViewModel @AssistedInject constructor( } private fun canViewReactions(event: TimelineEvent): Boolean { - // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values) return false + // Only event of type EventType.MESSAGE, EventType.STICKER, EventType.POLL_START, EventType.POLL_END are supported for the moment + if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values) return false return event.annotations?.reactionsSummary?.isNotEmpty() ?: false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index 2197d89a2c..09fccdcbee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.render import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage @@ -93,10 +94,13 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( ) } repliedToEvent.isPoll() -> { + val fallbackText = if (repliedToEvent.type in EventType.POLL_START.values) + stringProvider.getString(R.string.message_reply_to_sender_created_poll) + else stringProvider.getString(R.string.message_reply_to_sender_ended_poll) matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, - repliedToEvent.getPollQuestion() ?: stringProvider.getString(R.string.message_reply_to_sender_created_poll) + repliedToEvent.getPollQuestion() ?: fallbackText ) } repliedToEvent.isLiveLocation() -> { diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt index 51082e0e06..1244a0a108 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt @@ -78,6 +78,7 @@ class CheckIfCanReplyEventUseCaseTest { MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_FILE, MessageType.MSGTYPE_POLL_START, + MessageType.MSGTYPE_POLL_END, MessageType.MSGTYPE_BEACON_INFO, MessageType.MSGTYPE_LOCATION ) From 89f91a2ecd85fcd18115afba6296d236491a69fb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 14:37:20 +0300 Subject: [PATCH 05/15] Fix unit test. --- .../room/detail/timeline/style/TimelineMessageLayoutFactory.kt | 1 + .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index c207a5f67e..6e34aeeca2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -50,6 +50,7 @@ class TimelineMessageLayoutFactory @Inject constructor( EventType.STICKER, ) + EventType.POLL_START.values + + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values // Can't be rendered in bubbles, so get back to default layout diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index f612861511..ff10063d1a 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -29,6 +29,7 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage @@ -158,6 +159,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) + every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() @@ -168,6 +170,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) + every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns A_NEW_CONTENT executeAndAssertResult() From 89a7d708497098084f6dc8190f9e173a9dddeb29 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 2 Jan 2023 16:16:23 +0300 Subject: [PATCH 06/15] Implement reply preview for poll.end events. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../room/detail/composer/PlainTextComposerLayout.kt | 2 ++ .../detail/timeline/format/EventDetailsFormatter.kt | 5 +++++ .../render/ProcessBodyOfReplyToEventUseCase.kt | 10 +++++++--- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 2b8e397521..100635cc27 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3502,4 +3502,5 @@ sent a sticker. created a poll. ended a poll. + Ended poll diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt index 8f4dd9b71d..cf127d834f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -44,6 +44,7 @@ import org.commonmark.parser.Parser import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -181,6 +182,7 @@ class PlainTextComposerLayout @JvmOverloads constructor( is MessageAudioContent -> getAudioContentBodyText(messageContent) is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() is MessageBeaconInfoContent -> resources.getString(R.string.live_location_description) + is MessageEndPollContent -> resources.getString(R.string.message_reply_to_ended_poll_preview) else -> messageContent?.body.orEmpty() } var formattedBody: CharSequence? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt index 2233a53eda..f936093a3b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt @@ -17,11 +17,13 @@ package im.vector.app.features.home.room.detail.timeline.format import android.content.Context +import im.vector.app.R import im.vector.app.core.utils.TextUtils import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage +import org.matrix.android.sdk.api.session.events.model.isPollEnd import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -51,10 +53,13 @@ class EventDetailsFormatter @Inject constructor( event.isVideoMessage() -> formatForVideoMessage(event) event.isAudioMessage() -> formatForAudioMessage(event) event.isFileMessage() -> formatForFileMessage(event) + event.isPollEnd() -> formatPollEndMessage() else -> null } } + private fun formatPollEndMessage() = context.getString(R.string.message_reply_to_ended_poll_preview) + /** * Example: "1024 x 720 - 670 kB". */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index 09fccdcbee..dded85e186 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -26,6 +26,8 @@ import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isLiveLocation import org.matrix.android.sdk.api.session.events.model.isPoll +import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.events.model.isPollStart import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.isVoiceMessage @@ -94,9 +96,11 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( ) } repliedToEvent.isPoll() -> { - val fallbackText = if (repliedToEvent.type in EventType.POLL_START.values) - stringProvider.getString(R.string.message_reply_to_sender_created_poll) - else stringProvider.getString(R.string.message_reply_to_sender_ended_poll) + val fallbackText = when { + repliedToEvent.isPollStart() -> stringProvider.getString(R.string.message_reply_to_sender_created_poll) + repliedToEvent.isPollEnd() -> stringProvider.getString(R.string.message_reply_to_sender_ended_poll) + else -> "" + } matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, From f2359ccac297ce221a57740cc96fdb0e7dae0699 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 5 Jan 2023 14:54:16 +0300 Subject: [PATCH 07/15] Implement ended poll indicator. --- .../src/main/res/values/strings.xml | 1 + .../sdk/api/session/events/model/Event.kt | 4 ++-- .../timeline/factory/MessageItemFactory.kt | 7 +++++-- .../room/detail/timeline/item/PollItem.kt | 7 +++++++ .../res/layout/item_timeline_event_poll.xml | 20 +++++++++++++++---- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 100635cc27..69ae724e49 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3190,6 +3190,7 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll + Ended the poll. Share location diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 9b5f4ac19f..e84d67fecd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -219,7 +219,7 @@ data class Event( if (isRedacted()) return "Message removed" val text = getDecryptedValue() ?: run { if (isPoll()) { - return getPollQuestion() ?: "created a poll." + return getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null } return null } @@ -232,7 +232,7 @@ data class Event( isImageMessage() -> "sent an image." isVideoMessage() -> "sent a video." isSticker() -> "sent a sticker." - isPoll() -> getPollQuestion() ?: "created a poll." + isPoll() -> getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null isLiveLocation() -> "Live location." isLocationMessage() -> "has shared their location." else -> text diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index b4ba146176..a0cfcc77e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -204,7 +204,7 @@ class MessageItemFactory @Inject constructor( is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, 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, isEnded = false) is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) @@ -248,6 +248,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + isEnded: Boolean, ): PollItem { val pollViewState = pollItemViewStateFactory.create(pollContent, informationData) @@ -259,6 +260,7 @@ class MessageItemFactory @Inject constructor( .votesStatus(pollViewState.votesStatus) .optionViewStates(pollViewState.optionViewStates.orEmpty()) .edited(informationData.hasBeenEdited) + .ended(isEnded) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) @@ -286,7 +288,8 @@ class MessageItemFactory @Inject constructor( aggregatedInformationData, highlight, callback, - attributes + attributes, + isEnded = true ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt index 54be4092ed..6fe19e9762 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.widget.LinearLayout import android.widget.TextView import androidx.core.view.children +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -50,6 +51,9 @@ abstract class PollItem : AbsMessageItem() { @EpoxyAttribute lateinit var optionViewStates: List + @EpoxyAttribute + var ended: Boolean = false + override fun getViewStubId() = STUB_ID override fun bind(holder: Holder) { @@ -75,6 +79,8 @@ abstract class PollItem : AbsMessageItem() { it.setOnClickListener { onPollItemClick(optionViewState) } } } + + holder.endedPollTextView.isVisible = ended } private fun onPollItemClick(optionViewState: PollOptionViewState) { @@ -89,6 +95,7 @@ abstract class PollItem : AbsMessageItem() { val questionTextView by bind(R.id.questionTextView) val optionsContainer by bind(R.id.optionsContainer) val votesStatusTextView by bind(R.id.optionsVotesStatusTextView) + val endedPollTextView by bind(R.id.endedPollTextView) } companion object { diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml index a3fa07ade2..9151fc68cf 100644 --- a/vector/src/main/res/layout/item_timeline_event_poll.xml +++ b/vector/src/main/res/layout/item_timeline_event_poll.xml @@ -2,9 +2,21 @@ + android:layout_height="wrap_content" + android:minWidth="@dimen/chat_bubble_fixed_size"> + + Date: Thu, 5 Jan 2023 15:42:32 +0300 Subject: [PATCH 08/15] Add changelog. --- changelog.d/7900.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7900.feature diff --git a/changelog.d/7900.feature b/changelog.d/7900.feature new file mode 100644 index 0000000000..c3cce1e0e6 --- /dev/null +++ b/changelog.d/7900.feature @@ -0,0 +1 @@ +Render ended polls From ad30ca867169dfffd28944735e928870f581e54f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 14:52:41 +0300 Subject: [PATCH 09/15] Lint fixes. --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 2 -- .../detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index a0cfcc77e7..0cbddffef9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -266,8 +266,6 @@ class MessageItemFactory @Inject constructor( .callback(callback) } - - private fun buildEndedPollItem( endedPollContent: MessageEndPollContent, informationData: MessageInformationData, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt index dded85e186..ff814d4cbc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.render import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage From b73485e7b378c9e05c6abac0e08d8fcff1418876 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 17:18:50 +0300 Subject: [PATCH 10/15] Fix unit tests. --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4dbb63fb10..08bc7ced09 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3176,6 +3176,7 @@ Final result based on %1$d votes End poll + winner option End this poll? This will stop people from being able to vote and will display the final results of the poll. diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index ff10063d1a..69bcb81d5f 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -159,7 +159,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() @@ -170,7 +170,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { // Given givenTypeOfRepliedEvent(isPollMessage = true) givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.type } returns EventType.POLL_START.unstable + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable every { fakeRepliedEvent.getPollQuestion() } returns A_NEW_CONTENT executeAndAssertResult() From f33372411b6d2e16555bc1332fd13c1df9105a52 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 6 Jan 2023 18:23:41 +0300 Subject: [PATCH 11/15] Lint fix. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 08bc7ced09..4731a6b5db 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3177,7 +3177,7 @@ End poll - winner option + winner option End this poll? This will stop people from being able to vote and will display the final results of the poll. End poll From 2b26f2b22161f305eca45e0f929a9ebeca3c538a Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 9 Jan 2023 16:00:24 +0300 Subject: [PATCH 12/15] Fix related event id is null issue. --- .../room/detail/timeline/factory/MessageItemFactory.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 0cbddffef9..824547b8d0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -111,8 +111,10 @@ import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent +import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.util.MimeTypes +import timber.log.Timber import javax.inject.Inject class MessageItemFactory @Inject constructor( @@ -205,7 +207,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes, isEnded = false) - is MessageEndPollContent -> buildEndedPollItem(messageContent, informationData, highlight, callback, attributes) + is MessageEndPollContent -> buildEndedPollItem(event.getRelationContent()?.eventId, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes) is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes) @@ -267,13 +269,15 @@ class MessageItemFactory @Inject constructor( } private fun buildEndedPollItem( - endedPollContent: MessageEndPollContent, + pollStartEventId: String?, informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): PollItem? { - val pollStartEventId = endedPollContent.relatesTo?.eventId ?: return null + pollStartEventId ?: return null.also { + Timber.e("### buildEndedPollItem. Cannot render poll end event because poll start event id is null") + } val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null From a8b111dc8c8a525b23537c1138eae08e23b5f9e8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 15:04:13 +0300 Subject: [PATCH 13/15] Code review fixes. --- .../src/main/res/values/strings.xml | 1 + .../sdk/api/session/events/model/Event.kt | 14 +++- .../timeline/factory/MessageItemFactory.kt | 6 +- .../timeline/format/EventDetailsFormatter.kt | 4 ++ .../helper/MessageInformationDataFactory.kt | 25 +------ .../helper/PollResponseDataFactory.kt | 67 +++++++++++++++++++ .../action/CheckIfCanReplyEventUseCaseTest.kt | 2 +- .../ProcessBodyOfReplyToEventUseCaseTest.kt | 11 +++ 8 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 81f4f27127..0a12e859b5 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3509,6 +3509,7 @@ sent a sticker. created a poll. ended a poll. + Poll Ended poll Access Token diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 357c2b9608..40c69ceb66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -248,7 +248,7 @@ data class Event( if (isRedacted()) return "Message removed" val text = getDecryptedValue() ?: run { if (isPoll()) { - return getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null + return getTextSummaryForPoll() } return null } @@ -261,13 +261,23 @@ data class Event( isImageMessage() -> "sent an image." isVideoMessage() -> "sent a video." isSticker() -> "sent a sticker." - isPoll() -> getPollQuestion() ?: if (isPollStart()) "created a poll." else if (isPollEnd()) "ended a poll." else null + isPoll() -> getTextSummaryForPoll() isLiveLocation() -> "Live location." isLocationMessage() -> "has shared their location." else -> text } } + private fun getTextSummaryForPoll(): String? { + val pollQuestion = getPollQuestion() + return when { + pollQuestion != null -> pollQuestion + isPollStart() -> "created a poll." + isPollEnd() -> "ended a poll." + else -> null + } + } + private fun Event.isQuote(): Boolean { if (isReplyRenderedInThread()) return false return getDecryptedValue("formatted_body")?.contains("
") ?: false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 824547b8d0..219ccbe11c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -281,13 +281,9 @@ class MessageItemFactory @Inject constructor( val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId) val pollContent = pollStartEvent?.root?.getClearContent()?.toModel() ?: return null - val aggregatedInformationData = informationData.copy( - pollResponseAggregatedSummary = messageInformationDataFactory.mapPollResponseSummary(pollStartEvent.annotations?.pollResponseSummary) - ) - return buildPollItem( pollContent, - aggregatedInformationData, + informationData, highlight, callback, attributes, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt index f936093a3b..1d3f016951 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.events.model.isAudioMessage import org.matrix.android.sdk.api.session.events.model.isFileMessage import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.events.model.isPollStart import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -53,11 +54,14 @@ class EventDetailsFormatter @Inject constructor( event.isVideoMessage() -> formatForVideoMessage(event) event.isAudioMessage() -> formatForAudioMessage(event) event.isFileMessage() -> formatForFileMessage(event) + event.isPollStart() -> formatPollMessage() event.isPollEnd() -> formatPollEndMessage() else -> null } } + private fun formatPollMessage() = context.getString(R.string.message_reply_to_poll_preview) + private fun formatPollEndMessage() = context.getString(R.string.message_reply_to_ended_poll_preview) /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index d356c8a7d2..3ee309425a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -23,8 +23,6 @@ import im.vector.app.core.extensions.localDateTime import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.app.features.home.room.detail.timeline.item.PollResponseData -import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory @@ -38,7 +36,6 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent -import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageType @@ -55,7 +52,8 @@ class MessageInformationDataFactory @Inject constructor( private val session: Session, private val dateFormatter: VectorDateFormatter, private val messageLayoutFactory: TimelineMessageLayoutFactory, - private val reactionsSummaryFactory: ReactionsSummaryFactory + private val reactionsSummaryFactory: ReactionsSummaryFactory, + private val pollResponseDataFactory: PollResponseDataFactory, ) { fun create(params: TimelineItemFactoryParams): MessageInformationData { @@ -100,7 +98,7 @@ class MessageInformationDataFactory @Inject constructor( memberName = event.senderInfo.disambiguatedDisplayName, messageLayout = messageLayout, reactionsSummary = reactionsSummaryFactory.create(event), - pollResponseAggregatedSummary = mapPollResponseSummary(event.annotations?.pollResponseSummary), + pollResponseAggregatedSummary = pollResponseDataFactory.create(event), hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> @@ -121,23 +119,6 @@ class MessageInformationDataFactory @Inject constructor( ) } - fun mapPollResponseSummary(pollResponseSummary: PollResponseAggregatedSummary?): PollResponseData? { - return pollResponseSummary?.let { - PollResponseData( - myVote = it.aggregatedContent?.myVote, - isClosed = it.closedTime != null, - votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> - PollVoteSummaryData( - total = votesSummary.value.total, - percentage = votesSummary.value.percentage - ) - }, - winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 - ) - } - } - private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) { event.root.toValidDecryptedEvent()?.let { session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt new file mode 100644 index 0000000000..c71d7d493f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 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.helper + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData +import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.isPollEnd +import org.matrix.android.sdk.api.session.room.getTimelineEvent +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber +import javax.inject.Inject + +class PollResponseDataFactory @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun create(event: TimelineEvent): PollResponseData? { + val pollResponseSummary = getPollResponseSummary(event) + return pollResponseSummary?.let { + PollResponseData( + myVote = it.aggregatedContent?.myVote, + isClosed = it.closedTime != null, + votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary -> + PollVoteSummaryData( + total = votesSummary.value.total, + percentage = votesSummary.value.percentage + ) + }, + winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, + totalVotes = it.aggregatedContent?.totalVotes ?: 0 + ) + } + } + + private fun getPollResponseSummary(event: TimelineEvent): PollResponseAggregatedSummary? { + if (event.root.isPollEnd()) { + val pollStartEventId = event.root.getRelationContent()?.eventId ?: return null.also { + Timber.e("### Cannot render poll end event because poll start event id is null") + } + return activeSessionHolder + .getSafeActiveSession() + ?.roomService() + ?.getRoom(event.roomId) + ?.getTimelineEvent(pollStartEventId) + ?.annotations + ?.pollResponseSummary + } + return event.annotations?.pollResponseSummary + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt index 1244a0a108..e6e75b2e20 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCaseTest.kt @@ -43,7 +43,7 @@ class CheckIfCanReplyEventUseCaseTest { @Test fun `given reply is allowed for the event type when use case is executed then result is true`() { - val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE + val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.POLL_END.values + EventType.MESSAGE eventTypes.forEach { eventType -> val event = givenAnEvent(eventType) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt index 69bcb81d5f..c38afe20ec 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt @@ -176,6 +176,17 @@ class ProcessBodyOfReplyToEventUseCaseTest { executeAndAssertResult() } + @Test + fun `given a replied event of type poll end message when process the formatted body then content is replaced by correct string`() { + // Given + givenTypeOfRepliedEvent(isPollMessage = true) + givenNewContentForId(R.string.message_reply_to_sender_ended_poll) + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_END.unstable + every { fakeRepliedEvent.getPollQuestion() } returns null + + executeAndAssertResult() + } + @Test fun `given a replied event of type live location message when process the formatted body then content is replaced by correct string`() { // Given From ec27c67940b2ef3d6b28e512b6cd4692a5d089c1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 19:14:30 +0300 Subject: [PATCH 14/15] Fix color of winning vote count. --- .../home/room/detail/timeline/item/PollOptionView.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt index 38e6c5e6d7..e8d636e20b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt @@ -25,6 +25,7 @@ import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.setAttributeTintedImageResource import im.vector.app.databinding.ItemPollOptionBinding +import im.vector.app.features.themes.ThemeUtils class PollOptionView @JvmOverloads constructor( context: Context, @@ -62,6 +63,10 @@ class PollOptionView @JvmOverloads constructor( views.optionCheckImageView.isVisible = false val drawableStart = if (state.isWinner) R.drawable.ic_poll_winner else 0 views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, 0, 0, 0) + views.optionVoteCountTextView.setTextColor( + if (state.isWinner) ThemeUtils.getColor(context, R.attr.colorPrimary) + else ThemeUtils.getColor(context, R.attr.vctr_content_secondary) + ) showVotes(state.voteCount, state.votePercentage) renderVoteSelection(state.isWinner) } From 8495536fd327c672757e4b0ac967793725ebff74 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Jan 2023 19:22:56 +0300 Subject: [PATCH 15/15] Code review fix. --- .../helper/PollResponseDataFactory.kt | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index c71d7d493f..533397b4d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -50,18 +50,22 @@ class PollResponseDataFactory @Inject constructor( } private fun getPollResponseSummary(event: TimelineEvent): PollResponseAggregatedSummary? { - if (event.root.isPollEnd()) { - val pollStartEventId = event.root.getRelationContent()?.eventId ?: return null.also { + return if (event.root.isPollEnd()) { + val pollStartEventId = event.root.getRelationContent()?.eventId + if (pollStartEventId.isNullOrEmpty()) { Timber.e("### Cannot render poll end event because poll start event id is null") + null + } else { + activeSessionHolder + .getSafeActiveSession() + ?.roomService() + ?.getRoom(event.roomId) + ?.getTimelineEvent(pollStartEventId) + ?.annotations + ?.pollResponseSummary } - return activeSessionHolder - .getSafeActiveSession() - ?.roomService() - ?.getRoom(event.roomId) - ?.getTimelineEvent(pollStartEventId) - ?.annotations - ?.pollResponseSummary + } else { + event.annotations?.pollResponseSummary } - return event.annotations?.pollResponseSummary } }