From f3874be33788f3cd45e2ad389834fd47a366f629 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Thu, 23 Dec 2021 16:12:00 +0100 Subject: [PATCH] [merge] Bring back old url preview code Already added back old layout in merge, but that's not enough Change-Id: I0c262f1a33890959bde77a0d54916a88c4ff2cc9 --- .../detail/timeline/item/MessageTextItem.kt | 8 +- .../detail/timeline/url/PreviewUrlView.kt | 60 +----- .../detail/timeline/url/PreviewUrlViewSc.kt | 187 ++++++++++++++++++ .../item_timeline_event_text_message_stub.xml | 4 +- 4 files changed, 198 insertions(+), 61 deletions(-) create mode 100755 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlViewSc.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt index 29978e59da..3092b46593 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -17,10 +17,8 @@ package im.vector.app.features.home.room.detail.timeline.item import android.content.Context -import android.graphics.Color import android.text.TextUtils import android.text.method.MovementMethod -import android.widget.LinearLayout import androidx.core.text.PrecomputedTextCompat import androidx.core.view.isVisible import androidx.core.widget.TextViewCompat @@ -36,7 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlUiState -import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlView +import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlViewSc import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse @@ -152,11 +150,11 @@ abstract class MessageTextItem : AbsMessageItem() { class Holder : AbsMessageItem.Holder(STUB_ID) { val messageView by bind(R.id.messageTextView) - val previewUrlView by bind(R.id.messageUrlPreview) + val previewUrlView by bind(R.id.messageUrlPreview) } inner class PreviewUrlViewUpdater : PreviewUrlRetriever.PreviewUrlRetrieverListener { - var previewUrlView: PreviewUrlView? = null + var previewUrlView: PreviewUrlViewSc? = null var holder: Holder? = null var imageContentRenderer: ImageContentRenderer? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt index 428e5bd48b..631f00819c 100755 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt @@ -23,7 +23,7 @@ import androidx.core.view.isVisible import com.google.android.material.card.MaterialCardView import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.databinding.ViewUrlPreviewScBinding +import im.vector.app.databinding.ViewUrlPreviewBinding import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.themes.ThemeUtils @@ -39,10 +39,7 @@ class PreviewUrlView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : MaterialCardView(context, attrs, defStyleAttr), View.OnClickListener { - private lateinit var views: ViewUrlPreviewScBinding - - var footerWidth: Int = 0 - var footerHeight: Int = 0 + private lateinit var views: ViewUrlPreviewBinding var delegate: TimelineEventController.PreviewUrlCallback? = null @@ -106,33 +103,11 @@ class PreviewUrlView @JvmOverloads constructor( } } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - // Get max available width - we're faking "wrap_content" here to use all available space, - // since match_parent doesn't work here as our parent does wrap_content as well - /* - val widthMode = MeasureSpec.getMode(widthMeasureSpec) - val widthSize = MeasureSpec.getSize(widthMeasureSpec) - val widthLimit = if (widthMode == MeasureSpec.AT_MOST) { - widthSize.toFloat() - } else { - Float.MAX_VALUE - } - */ - //setMeasuredDimension(round(widthLimit).toInt(), measuredHeight) - - // We extract the size from an AT_MOST spec, which is the width limit, but change the mode to EXACTLY - val newWidthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY) - - // We measure our children based on the now fixed width - super.onMeasure(newWidthSpec, heightMeasureSpec) - - } - // PRIVATE METHODS **************************************************************************************************************************************** private fun setupView() { - inflate(context, R.layout.view_url_preview_sc, this) - views = ViewUrlPreviewScBinding.bind(this) + inflate(context, R.layout.view_url_preview, this) + views = ViewUrlPreviewBinding.bind(this) setOnClickListener(this) views.urlPreviewImage.setOnClickListener { onImageClick() } @@ -149,21 +124,17 @@ class PreviewUrlView @JvmOverloads constructor( } private fun renderData(previewUrlData: PreviewUrlData, imageContentRenderer: ImageContentRenderer) { - // Set footer sizes before setText() calls so they are available onMeasure - val siteText = previewUrlData.siteName.takeIf { it != previewUrlData.title } - updateFooterSpaceInternal(siteText) - isVisible = true views.urlPreviewTitle.setTextOrHide(previewUrlData.title) - views.urlPreviewImage.isVisible = previewUrlData.mxcUrl?.let { imageContentRenderer.render(it, views.urlPreviewImage, hideOnFail = true) }.orFalse() + views.urlPreviewImage.isVisible = previewUrlData.mxcUrl?.let { imageContentRenderer.render(it, views.urlPreviewImage) }.orFalse() views.urlPreviewDescription.setTextOrHide(previewUrlData.description) views.urlPreviewDescription.maxLines = when { previewUrlData.mxcUrl != null -> 2 previewUrlData.title != null -> 3 else -> 5 } - views.urlPreviewSite.setTextOrHide(siteText) + views.urlPreviewSite.setTextOrHide(previewUrlData.siteName.takeIf { it != previewUrlData.title }) } /** @@ -175,23 +146,4 @@ class PreviewUrlView @JvmOverloads constructor( views.urlPreviewDescription.isVisible = false views.urlPreviewSite.isVisible = false } - - public fun updateFooterSpace() { - val siteText = views.urlPreviewSite.text as String? - updateFooterSpaceInternal(siteText) - requestLayout() - } - - private fun updateFooterSpaceInternal(siteText: String?) { - val siteViewHidden = siteText == null || siteText.isBlank() // identical to setTextOrHide - if (siteViewHidden) { - views.urlPreviewDescription.footerWidth = footerWidth - views.urlPreviewDescription.footerHeight = footerHeight - } else { - views.urlPreviewSite.footerWidth = footerWidth - views.urlPreviewSite.footerHeight = footerHeight - views.urlPreviewDescription.footerWidth = 0 - views.urlPreviewDescription.footerHeight = 0 - } - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlViewSc.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlViewSc.kt new file mode 100755 index 0000000000..6d13e5a36a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlViewSc.kt @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020 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.url + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.databinding.ViewUrlPreviewScBinding +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.media.ImageContentRenderer +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.media.PreviewUrlData + +/** + * A View to display a PreviewUrl and some other state + */ +class PreviewUrlViewSc @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener { + + private lateinit var views: ViewUrlPreviewScBinding + + var footerWidth: Int = 0 + var footerHeight: Int = 0 + + var delegate: TimelineEventController.PreviewUrlCallback? = null + + init { + setupView() + } + + private var state: PreviewUrlUiState = PreviewUrlUiState.Unknown + + /** + * This methods is responsible for rendering the view according to the newState + * + * @param newState the newState representing the view + */ + fun render(newState: PreviewUrlUiState, + imageContentRenderer: ImageContentRenderer, + force: Boolean = false) { + if (newState == state && !force) { + return + } + + state = newState + + hideAll() + when (newState) { + PreviewUrlUiState.Unknown, + PreviewUrlUiState.NoUrl -> renderHidden() + PreviewUrlUiState.Loading -> renderLoading() + is PreviewUrlUiState.Error -> renderHidden() + is PreviewUrlUiState.Data -> renderData(newState.previewUrlData, imageContentRenderer) + } + } + + override fun onClick(v: View?) { + when (val finalState = state) { + is PreviewUrlUiState.Data -> delegate?.onPreviewUrlClicked(finalState.url) + else -> Unit + } + } + + private fun onImageClick() { + when (val finalState = state) { + is PreviewUrlUiState.Data -> { + delegate?.onPreviewUrlImageClicked( + sharedView = views.urlPreviewImage, + mxcUrl = finalState.previewUrlData.mxcUrl, + title = finalState.previewUrlData.title + ) + } + else -> Unit + } + } + + private fun onCloseClick() { + when (val finalState = state) { + is PreviewUrlUiState.Data -> delegate?.onPreviewUrlCloseClicked(finalState.eventId, finalState.url) + else -> Unit + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // Get max available width - we're faking "wrap_content" here to use all available space, + // since match_parent doesn't work here as our parent does wrap_content as well + /* + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val widthLimit = if (widthMode == MeasureSpec.AT_MOST) { + widthSize.toFloat() + } else { + Float.MAX_VALUE + } + */ + //setMeasuredDimension(round(widthLimit).toInt(), measuredHeight) + + // We extract the size from an AT_MOST spec, which is the width limit, but change the mode to EXACTLY + val newWidthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY) + + // We measure our children based on the now fixed width + super.onMeasure(newWidthSpec, heightMeasureSpec) + + } + + // PRIVATE METHODS **************************************************************************************************************************************** + + private fun setupView() { + inflate(context, R.layout.view_url_preview_sc, this) + views = ViewUrlPreviewScBinding.bind(this) + + setOnClickListener(this) + views.urlPreviewImage.setOnClickListener { onImageClick() } + views.urlPreviewClose.setOnClickListener { onCloseClick() } + } + + private fun renderHidden() { + isVisible = false + } + + private fun renderLoading() { + // Just hide for the moment + isVisible = false + } + + private fun renderData(previewUrlData: PreviewUrlData, imageContentRenderer: ImageContentRenderer) { + // Set footer sizes before setText() calls so they are available onMeasure + val siteText = previewUrlData.siteName.takeIf { it != previewUrlData.title } + updateFooterSpaceInternal(siteText) + + isVisible = true + views.urlPreviewTitle.setTextOrHide(previewUrlData.title) + views.urlPreviewImage.isVisible = previewUrlData.mxcUrl?.let { imageContentRenderer.render(it, views.urlPreviewImage, hideOnFail = true) }.orFalse() + views.urlPreviewDescription.setTextOrHide(previewUrlData.description) + views.urlPreviewSite.setTextOrHide(siteText) + } + + /** + * Hide all views that are not visible in all state + */ + private fun hideAll() { + views.urlPreviewTitle.isVisible = false + views.urlPreviewImage.isVisible = false + views.urlPreviewDescription.isVisible = false + views.urlPreviewSite.isVisible = false + } + + public fun updateFooterSpace() { + val siteText = views.urlPreviewSite.text as String? + updateFooterSpaceInternal(siteText) + requestLayout() + } + + private fun updateFooterSpaceInternal(siteText: String?) { + val siteViewHidden = siteText == null || siteText.isBlank() // identical to setTextOrHide + if (siteViewHidden) { + views.urlPreviewDescription.footerWidth = footerWidth + views.urlPreviewDescription.footerHeight = footerHeight + } else { + views.urlPreviewSite.footerWidth = footerWidth + views.urlPreviewSite.footerHeight = footerHeight + views.urlPreviewDescription.footerWidth = 0 + views.urlPreviewDescription.footerHeight = 0 + } + } +} diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index 2a6e3dafd0..466a6e1d81 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -25,9 +25,9 @@ tools:text="@sample/messages.json/data/message" tools:ignore="RtlHardcoded" /> -