Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reply to a poll #1848

Merged
merged 3 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1848.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reply to a poll
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,10 @@ class MessagesPresenter @AssistedInject constructor(
is TimelineItemLocationContent -> AttachmentThumbnailInfo(
type = AttachmentThumbnailType.Location,
)
is TimelineItemPollContent, // TODO Polls: handle reply to
is TimelineItemPollContent -> AttachmentThumbnailInfo(
textContent = targetEvent.content.question,
type = AttachmentThumbnailType.Poll,
)
is TimelineItemTextBasedContent,
is TimelineItemRedactedContent,
is TimelineItemStateContent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,10 @@ class ActionListPresenter @Inject constructor(
is TimelineItemPollContent -> {
buildList {
val isMineOrCanRedact = timelineItem.isMine || userCanRedact

// TODO Polls: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()`
// when touching this
// if (timelineItem.isRemote) {
// // Can only reply or forward messages already uploaded to the server
// add(TimelineItemAction.Reply)
// }
if (timelineItem.isRemote) {
// Can only reply or forward messages already uploaded to the server
add(TimelineItemAction.Reply)
}
if (!timelineItem.content.isEnded && timelineItem.isRemote && isMineOrCanRedact) {
add(TimelineItemAction.EndPoll)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
val textContent = remember(event.content) { formatter.format(event) }

when (event.content) {
is TimelineItemPollContent, // TODO Polls: handle summary
is TimelineItemTextBasedContent,
is TimelineItemStateContent,
is TimelineItemEncryptedContent,
Expand Down Expand Up @@ -317,6 +316,18 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
}
content = { ContentForBody(textContent) }
}
is TimelineItemPollContent -> {
icon = {
AttachmentThumbnail(
modifier = imageModifier,
info = AttachmentThumbnailInfo(
textContent = textContent,
type = AttachmentThumbnailType.Poll,
)
)
}
content = { ContentForBody(textContent) }
}
}
Row(modifier = modifier) {
icon()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
Expand Down Expand Up @@ -638,35 +639,41 @@ private fun ReplyToContent(
}

private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready): AttachmentThumbnailInfo? {
val messageContent = inReplyTo.content as? MessageContent ?: return null
return when (val type = messageContent.type) {
is ImageMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource ?: type.source,
textContent = messageContent.body,
type = AttachmentThumbnailType.Image,
blurHash = type.info?.blurhash,
)
is VideoMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource,
textContent = messageContent.body,
type = AttachmentThumbnailType.Video,
blurHash = type.info?.blurhash,
)
is FileMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource,
textContent = messageContent.body,
type = AttachmentThumbnailType.File,
)
is LocationMessageType -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Location,
)
is AudioMessageType -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Audio,
)
is VoiceMessageType -> AttachmentThumbnailInfo(
type = AttachmentThumbnailType.Voice,
return when (val eventContent = inReplyTo.content) {
is MessageContent -> when (val type = eventContent.type) {
is ImageMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource ?: type.source,
textContent = eventContent.body,
type = AttachmentThumbnailType.Image,
blurHash = type.info?.blurhash,
)
is VideoMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource,
textContent = eventContent.body,
type = AttachmentThumbnailType.Video,
blurHash = type.info?.blurhash,
)
is FileMessageType -> AttachmentThumbnailInfo(
thumbnailSource = type.info?.thumbnailSource,
textContent = eventContent.body,
type = AttachmentThumbnailType.File,
)
is LocationMessageType -> AttachmentThumbnailInfo(
textContent = eventContent.body,
type = AttachmentThumbnailType.Location,
)
is AudioMessageType -> AttachmentThumbnailInfo(
textContent = eventContent.body,
type = AttachmentThumbnailType.Audio,
)
is VoiceMessageType -> AttachmentThumbnailInfo(
type = AttachmentThumbnailType.Voice,
)
else -> null
}
is PollContent -> AttachmentThumbnailInfo(
textContent = eventContent.question,
type = AttachmentThumbnailType.Poll,
)
else -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ fun TimelineItemEventContent.canBeCopied(): Boolean =
fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
when (this) {
is TimelineItemRedactedContent,
is TimelineItemStateContent,
is TimelineItemPollContent -> false
is TimelineItemStateContent -> false
else -> true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
import io.element.android.features.messages.media.FakeLocalMediaFactory
Expand Down Expand Up @@ -612,6 +613,28 @@ class MessagesPresenterTest {
}
}

@Test
fun `present - handle action reply to a poll`() = runTest {
val presenter = createMessagesPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
val poll = aMessageEvent(
content = aTimelineItemPollContent()
)
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll))
val finalState = awaitItem()
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
assertThat(replyMode.attachmentThumbnailInfo).isNotNull()
assertThat(replyMode.attachmentThumbnailInfo?.textContent)
.isEqualTo("What type of food should we have at the party?")
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
}
}

private fun TestScope.createMessagesPresenter(
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ class ActionListPresenterTest {
ActionListState.Target.Success(
messageEvent,
persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.EndPoll,
TimelineItemAction.Redact,
)
Expand All @@ -452,6 +453,7 @@ class ActionListPresenterTest {
ActionListState.Target.Success(
messageEvent,
persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Redact,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.GraphicEq
import androidx.compose.material.icons.outlined.Image
import androidx.compose.material.icons.outlined.Poll
import androidx.compose.material.icons.outlined.VideoCameraBack
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -122,14 +123,20 @@ fun AttachmentThumbnail(
)
*/
}
AttachmentThumbnailType.Poll -> {
Icon(
imageVector = Icons.Outlined.Poll,
contentDescription = info.textContent,
)
}
}
}
}
}

@Parcelize
enum class AttachmentThumbnailType : Parcelable {
Image, Video, File, Audio, Location, Voice
Image, Video, File, Audio, Location, Voice, Poll
}

@Parcelize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ open class AttachmentThumbnailInfoProvider : PreviewParameterProvider<Attachment
anAttachmentThumbnailInfo(type = AttachmentThumbnailType.File),
anAttachmentThumbnailInfo(type = AttachmentThumbnailType.Location),
anAttachmentThumbnailInfo(type = AttachmentThumbnailType.Voice),
anAttachmentThumbnailInfo(type = AttachmentThumbnailType.Poll),
)
}

Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange that the "Reply" action was already present, I guess the state for the preview was not conform to what could happen in real life. It's OK now!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had built it earlier but then didn't enable it because there was a bug in the rust side.
So some of the infrastructure for replies was already in place albeit not finished.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.