Render images in replies
Change-Id: Ia0678184c42a2a01f2e1e65ccd8287bbd71c8c80
This commit is contained in:
parent
048e1edb54
commit
8e97b7c79d
|
@ -43,4 +43,7 @@
|
|||
|
||||
<dimen name="file_icon_size">32dp</dimen>
|
||||
|
||||
<dimen name="reply_thumbnail_height">120dp</dimen>
|
||||
<dimen name="reply_thumbnail_max_width">840dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -70,6 +70,7 @@ import im.vector.app.features.html.SpanUtils
|
|||
import im.vector.app.features.html.VectorHtmlCompressor
|
||||
import im.vector.app.features.location.live.StopLiveLocationShareUseCase
|
||||
import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.raw.wellknown.CryptoConfig
|
||||
|
@ -173,6 +174,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||
htmlCompressor: VectorHtmlCompressor,
|
||||
htmlRenderer: EventHtmlRenderer,
|
||||
spanUtils: SpanUtils,
|
||||
imageContentRenderer: ImageContentRenderer,
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback,
|
||||
ReplyPreviewRetriever.PowerLevelProvider, ReplyPreviewRetriever.PreviewReplyRetrieverCallback {
|
||||
|
@ -199,7 +201,8 @@ class TimelineViewModel @AssistedInject constructor(
|
|||
messageColorProvider,
|
||||
htmlCompressor,
|
||||
htmlRenderer,
|
||||
spanUtils
|
||||
spanUtils,
|
||||
imageContentRenderer,
|
||||
)
|
||||
|
||||
override fun resolveDisplayName(senderInfo: SenderInfo): String = (timeline?.senderWithLiveRoomState(senderInfo) ?: senderInfo).disambiguatedDisplayName
|
||||
|
|
|
@ -273,7 +273,15 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder>(
|
|||
|
||||
override fun onStateUpdated(state: PreviewReplyUiState) {
|
||||
replyPreviewRetriever?.let {
|
||||
replyView?.render(state, it, attributes.informationData, movementMethod, attributes.itemLongClickListener, coroutineScope)
|
||||
replyView?.render(
|
||||
state,
|
||||
it,
|
||||
attributes.informationData,
|
||||
movementMethod,
|
||||
attributes.itemLongClickListener,
|
||||
coroutineScope,
|
||||
attributes.generateMissingVideoThumbnails
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.core.text.PrecomputedTextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.extensions.tintBackground
|
||||
import im.vector.app.databinding.ViewInReplyToBinding
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
|
@ -41,10 +43,18 @@ import im.vector.app.features.home.room.detail.timeline.item.BindingOptions
|
|||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getCaption
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileName
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import timber.log.Timber
|
||||
|
@ -68,6 +78,9 @@ class InReplyToView @JvmOverloads constructor(
|
|||
|
||||
private var state: PreviewReplyUiState = PreviewReplyUiState.NoReply
|
||||
|
||||
private val maxThumbnailWidth = context.resources.getDimensionPixelSize(R.dimen.reply_thumbnail_max_width)
|
||||
private val maxThumbnailHeight = context.resources.getDimensionPixelSize(R.dimen.reply_thumbnail_height)
|
||||
|
||||
/**
|
||||
* This methods is responsible for rendering the view according to the newState
|
||||
*
|
||||
|
@ -79,7 +92,9 @@ class InReplyToView @JvmOverloads constructor(
|
|||
movementMethod: MovementMethod?,
|
||||
itemLongClickListener: OnLongClickListener?,
|
||||
coroutineScope: CoroutineScope,
|
||||
force: Boolean = false) {
|
||||
generateMissingVideoThumbnails: Boolean,
|
||||
force: Boolean = false
|
||||
) {
|
||||
if (newState == state && !force) {
|
||||
return
|
||||
}
|
||||
|
@ -90,7 +105,14 @@ class InReplyToView @JvmOverloads constructor(
|
|||
PreviewReplyUiState.NoReply -> renderHidden()
|
||||
is PreviewReplyUiState.ReplyLoading -> renderLoading()
|
||||
is PreviewReplyUiState.Error -> renderError(newState)
|
||||
is PreviewReplyUiState.InReplyTo -> renderReplyTo(newState, retriever, roomInformationData, movementMethod, coroutineScope)
|
||||
is PreviewReplyUiState.InReplyTo -> renderReplyTo(
|
||||
newState,
|
||||
retriever,
|
||||
roomInformationData,
|
||||
movementMethod,
|
||||
coroutineScope,
|
||||
generateMissingVideoThumbnails
|
||||
)
|
||||
}
|
||||
|
||||
setOnLongClickListener(itemLongClickListener)
|
||||
|
@ -117,6 +139,7 @@ class InReplyToView @JvmOverloads constructor(
|
|||
private fun hideViews() {
|
||||
views.replyMemberNameView.isVisible = false
|
||||
views.replyTextView.isVisible = false
|
||||
views.replyThumbnailView.isVisible = false
|
||||
renderFadeOut(null)
|
||||
}
|
||||
|
||||
|
@ -154,7 +177,8 @@ class InReplyToView @JvmOverloads constructor(
|
|||
retriever: ReplyPreviewRetriever,
|
||||
roomInformationData: MessageInformationData,
|
||||
movementMethod: MovementMethod?,
|
||||
coroutineScope: CoroutineScope
|
||||
coroutineScope: CoroutineScope,
|
||||
generateMissingVideoThumbnails: Boolean,
|
||||
) {
|
||||
hideViews()
|
||||
isVisible = true
|
||||
|
@ -169,6 +193,8 @@ class InReplyToView @JvmOverloads constructor(
|
|||
renderFadeOut(roomInformationData)
|
||||
when (val content = state.event.getLastMessageContent()) {
|
||||
is MessageTextContent -> renderTextContent(content, retriever, movementMethod, coroutineScope)
|
||||
is MessageImageInfoContent -> renderImageThumbnailContent(content, state.event, retriever)
|
||||
is MessageVideoContent -> renderVideoThumbnailContent(content, state.event, retriever, generateMissingVideoThumbnails)
|
||||
else -> renderFallback(state.event, retriever)
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +247,68 @@ class InReplyToView @JvmOverloads constructor(
|
|||
markwonPlugins.forEach { plugin -> plugin.afterSetText(views.replyTextView) }
|
||||
}
|
||||
|
||||
private fun renderImageThumbnailContent(
|
||||
content: MessageImageInfoContent,
|
||||
event: TimelineEvent,
|
||||
retriever: ReplyPreviewRetriever,
|
||||
) {
|
||||
val data = ImageContentRenderer.Data(
|
||||
eventId = event.eventId,
|
||||
filename = content.getFileName(),
|
||||
caption = content.getCaption(),
|
||||
mimeType = content.mimeType,
|
||||
url = content.getFileUrl(),
|
||||
elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
|
||||
height = content.info?.height,
|
||||
maxHeight = maxThumbnailHeight,
|
||||
width = content.info?.width,
|
||||
maxWidth = maxThumbnailWidth,
|
||||
allowNonMxcUrls = false
|
||||
)
|
||||
|
||||
renderThumbnailContent(data, retriever)
|
||||
}
|
||||
|
||||
private fun renderVideoThumbnailContent(
|
||||
content: MessageVideoContent,
|
||||
event: TimelineEvent,
|
||||
retriever: ReplyPreviewRetriever,
|
||||
generateMissingVideoThumbnails: Boolean,
|
||||
) {
|
||||
val thumbnailData = ImageContentRenderer.Data(
|
||||
eventId = event.eventId,
|
||||
filename = content.getFileName(),
|
||||
caption = content.getCaption(),
|
||||
mimeType = content.mimeType,
|
||||
url = content.videoInfo?.getThumbnailUrl(),
|
||||
elementToDecrypt = content.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = content.videoInfo?.height,
|
||||
maxHeight = maxThumbnailHeight,
|
||||
width = content.videoInfo?.width,
|
||||
maxWidth = maxThumbnailWidth,
|
||||
allowNonMxcUrls = false,
|
||||
// Video fallback for generating thumbnails
|
||||
downloadFallbackIfThumbnailMissing = generateMissingVideoThumbnails,
|
||||
fallbackUrl = content.getFileUrl(),
|
||||
fallbackElementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt()
|
||||
)
|
||||
renderThumbnailContent(thumbnailData, retriever)
|
||||
}
|
||||
|
||||
private fun renderThumbnailContent(
|
||||
mediaData: ImageContentRenderer.Data,
|
||||
retriever: ReplyPreviewRetriever,
|
||||
) {
|
||||
views.replyThumbnailView.isVisible = true
|
||||
retriever.imageContentRenderer.render(
|
||||
mediaData,
|
||||
ImageContentRenderer.Mode.THUMBNAIL,
|
||||
views.replyThumbnailView,
|
||||
animate = false
|
||||
)
|
||||
views.replyTextView.setTextOrHide(mediaData.caption)
|
||||
}
|
||||
|
||||
private fun renderFallback(event: TimelineEvent, retriever: ReplyPreviewRetriever) {
|
||||
views.replyTextView.isVisible = true
|
||||
views.replyTextView.text = retriever.formatFallbackReply(event)
|
||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.reply
|
|||
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
|
@ -28,6 +27,7 @@ import im.vector.app.features.html.EventHtmlRenderer
|
|||
import im.vector.app.features.html.PillsPostProcessor
|
||||
import im.vector.app.features.html.SpanUtils
|
||||
import im.vector.app.features.html.VectorHtmlCompressor
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -42,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
|||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
@ -60,6 +59,7 @@ class ReplyPreviewRetriever(
|
|||
val htmlCompressor: VectorHtmlCompressor,
|
||||
val htmlRenderer: EventHtmlRenderer,
|
||||
val spanUtils: SpanUtils,
|
||||
val imageContentRenderer: ImageContentRenderer,
|
||||
) {
|
||||
private data class ReplyPreviewUiState(
|
||||
// Id of the latest event in the case of an edited event, or the eventId for an event which has not been edited
|
||||
|
|
|
@ -31,6 +31,18 @@
|
|||
android:textSize="12sp"
|
||||
tools:text="@sample/users.json/data/displayName" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/replyThumbnailView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/reply_thumbnail_height"
|
||||
android:contentDescription="@string/a11y_image"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_gravity="start"
|
||||
android:scaleType="fitStart"
|
||||
tools:layout_width="200dp"
|
||||
app:layout_goneMarginTop="0dp"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<com.ruesga.rview.widget.ExpandableViewLayout
|
||||
android:id="@+id/expandableReplyView"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Reference in New Issue