Reply to a poll (#1848)
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request --> ## Type of change - [x] Feature - [ ] Bugfix - [ ] Technical - [ ] Other : ## Content Polls can now be replied to. ## Motivation and context User story: https://github.com/vector-im/element-meta/issues/1976 ## Screenshots / GIFs <!-- We have screenshot tests in the project, so attaching screenshots to a PR is not mandatory, as far as there is a Composable Preview covering the changes. In this case, the change will appear in the file diff. Note that all the UI composables should be covered by a Composable Preview. Providing a video of the change is still very useful for the reviewer and for the history of the project. You can use a table like this to show screenshots comparison. Uncomment this markdown table below and edit the last line `|||`: |copy screenshot of before here|copy screenshot of after here| |Before|After| |-|-| ||| --> ## Tests <!-- Explain how you tested your development --> - Step 1 - Step 2 - Step ... ## Tested devices - [ ] Physical - [ ] Emulator - OS version(s): ## Checklist <!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. --> - [ ] Changes have been tested on an Android device or Android emulator with API 23 - [ ] UI change has been tested on both light and dark themes - [ ] Accessibility has been taken into account. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#accessibility - [ ] Pull request is based on the develop branch - [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-x-android/blob/develop/CONTRIBUTING.md#changelog - [ ] Pull request includes screenshots or videos if containing UI changes - [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off) - [ ] You've made a self review of your PR
This commit is contained in:
1
changelog.d/1848.feature
Normal file
1
changelog.d/1848.feature
Normal file
@@ -0,0 +1 @@
|
||||
Reply to a poll
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -426,6 +426,7 @@ class ActionListPresenterTest {
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.EndPoll,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
@@ -452,6 +453,7 @@ class ActionListPresenterTest {
|
||||
ActionListState.Target.Success(
|
||||
messageEvent,
|
||||
persistentListOf(
|
||||
TimelineItemAction.Reply,
|
||||
TimelineItemAction.Redact,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -122,6 +123,12 @@ fun AttachmentThumbnail(
|
||||
)
|
||||
*/
|
||||
}
|
||||
AttachmentThumbnailType.Poll -> {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Poll,
|
||||
contentDescription = info.textContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +136,7 @@ fun AttachmentThumbnail(
|
||||
|
||||
@Parcelize
|
||||
enum class AttachmentThumbnailType : Parcelable {
|
||||
Image, Video, File, Audio, Location, Voice
|
||||
Image, Video, File, Audio, Location, Voice, Poll
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user