[Message actions] New UI for replies (#545)

* Add 'reply to' UI to the message composer.

* Move the `BlurHashAsyncImage` to `:libraries:designsystem` as it is now used in several modules.

*  Create reusable `AttachmentThumbnail` and associated data classes and enums, it's now added to `:libraries:matrixui`.

* Re-use `AttachmentThumbnail` in a `ActionListView` and `TextComposer`.

* Add 'inReplyTo' models and UI.

* Add min size for images

* Create a separate layout for media items with no reply to info. Also, separate `Timeline__Row` components from `TimelineView`, as it was getting too large.

* Added `EqualWidthColumn` to use inside message bubbles. Also fixed some modifiers for media items replying to other messages.

* Disable `inReplyToClicked`.

* Remove unused resources and libraries.

* Remove any traces of `BlurHashAsyncImage` in `:features:messages`, since it was moved to the design system.

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-06-08 12:15:13 +02:00 committed by GitHub
parent 1ea4e96497
commit c176eab4a3
56 changed files with 1253 additions and 1008 deletions

View file

@ -16,12 +16,19 @@
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.TimelineItem
import timber.log.Timber
class MatrixTimelineItemMapper(
private val room: Room,
private val coroutineScope: CoroutineScope,
private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(),
private val eventTimelineItemMapper: EventTimelineItemMapper= EventTimelineItemMapper(),
) {
@ -30,6 +37,12 @@ class MatrixTimelineItemMapper(
val asEvent = it.asEvent()
if (asEvent != null) {
val eventTimelineItem = eventTimelineItemMapper.map(asEvent)
if (eventTimelineItem.hasNotLoadedInReplyTo() && eventTimelineItem.eventId != null) {
fetchDetailsForEvent(eventTimelineItem.eventId!!)
}
return MatrixTimelineItem.Event(eventTimelineItem)
}
val asVirtual = it.asVirtual()
@ -39,4 +52,13 @@ class MatrixTimelineItemMapper(
}
return MatrixTimelineItem.Other
}
private fun fetchDetailsForEvent(eventId: EventId) = coroutineScope.launch {
runCatching {
room.fetchDetailsForEvent(eventId.value)
}.onFailure {
Timber.e(it)
}
}
}

View file

@ -63,6 +63,8 @@ class RustMatrixTimeline(
)
private val timelineItemFactory = MatrixTimelineItemMapper(
room = innerRoom,
coroutineScope = coroutineScope,
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
eventTimelineItemMapper = EventTimelineItemMapper(
contentMapper = TimelineEventContentMapper(

View file

@ -17,11 +17,13 @@
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
@ -31,6 +33,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageT
import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.Message
import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.ProfileDetails
import org.matrix.rustcomponents.sdk.RepliedToEventDetails
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
@ -66,9 +70,26 @@ class EventMessageMapper {
}
}
}
val inReplyToId = it.inReplyTo()?.eventId?.let(::EventId)
val inReplyToEvent: InReplyTo? = (it.inReplyTo()?.event)?.use { details ->
when (details) {
is RepliedToEventDetails.Ready -> {
val senderProfile = details.senderProfile as? ProfileDetails.Ready
InReplyTo.Ready(
eventId = inReplyToId!!,
content = map(details.message),
senderId = UserId(details.sender),
senderDisplayName = senderProfile?.displayName,
senderAvatarUrl = senderProfile?.avatarUrl,
)
}
is RepliedToEventDetails.Error -> InReplyTo.Error
is RepliedToEventDetails.Pending, is RepliedToEventDetails.Unavailable -> InReplyTo.NotLoaded(inReplyToId!!)
}
}
MessageContent(
body = it.body(),
inReplyTo = it.inReplyTo()?.eventId?.let(::EventId),
inReplyTo = inReplyToEvent,
isEdited = it.isEdited(),
type = type
)