Merge pull request #2793 from vector-im/feature/bma/url_preview_fixes
Url preview fixes
This commit is contained in:
commit
28fad01be7
@ -5,12 +5,14 @@ Features ✨:
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
- Open image from URL Preview (#2705)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Bug in WidgetContent.computeURL() (#2767)
|
||||
- Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776)
|
||||
- Join room by alias other federation error (#2778)
|
||||
- HTML unescaping for URL preview (#2766)
|
||||
- URL preview on reply fallback (#2756)
|
||||
- RTL: some arrows should be rotated in RTL (#2757)
|
||||
|
||||
Translations 🗣:
|
||||
|
@ -20,7 +20,7 @@ object ContentUtils {
|
||||
val lines = repliedBody.lines()
|
||||
var wellFormed = repliedBody.startsWith(">")
|
||||
var endOfPreviousFound = false
|
||||
val usefullines = ArrayList<String>()
|
||||
val usefulLines = ArrayList<String>()
|
||||
lines.forEach {
|
||||
if (it == "") {
|
||||
endOfPreviousFound = true
|
||||
@ -29,10 +29,10 @@ object ContentUtils {
|
||||
if (!endOfPreviousFound) {
|
||||
wellFormed = wellFormed && it.startsWith(">")
|
||||
} else {
|
||||
usefullines.add(it)
|
||||
usefulLines.add(it)
|
||||
}
|
||||
}
|
||||
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||
return usefulLines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||
}
|
||||
|
||||
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
|
||||
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.matrix.android.sdk.internal.util.unescapeHtml
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -73,9 +74,9 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
|
||||
private fun JsonDict.toPreviewUrlData(url: String): PreviewUrlData {
|
||||
return PreviewUrlData(
|
||||
url = (get("og:url") as? String) ?: url,
|
||||
siteName = get("og:site_name") as? String,
|
||||
title = get("og:title") as? String,
|
||||
description = get("og:description") as? String,
|
||||
siteName = (get("og:site_name") as? String)?.unescapeHtml(),
|
||||
title = (get("og:title") as? String)?.unescapeHtml(),
|
||||
description = (get("og:description") as? String)?.unescapeHtml(),
|
||||
mxcUrl = get("og:image") as? String
|
||||
)
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.isReply
|
||||
import org.matrix.android.sdk.api.util.ContentUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class UrlsExtractor @Inject constructor() {
|
||||
@ -35,7 +37,14 @@ internal class UrlsExtractor @Inject constructor() {
|
||||
|| it.msgType == MessageType.MSGTYPE_NOTICE
|
||||
|| it.msgType == MessageType.MSGTYPE_EMOTE
|
||||
}
|
||||
?.body
|
||||
?.let { messageContent ->
|
||||
if (event.isReply()) {
|
||||
// This is a reply, strip the reply fallback
|
||||
ContentUtils.extractUsefulTextFromReply(messageContent.body)
|
||||
} else {
|
||||
messageContent.body
|
||||
}
|
||||
}
|
||||
?.let { urlRegex.findAll(it) }
|
||||
?.map { it.value }
|
||||
?.filter { it.startsWith("https://") || it.startsWith("http://") }
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.util
|
||||
|
||||
import androidx.core.text.HtmlCompat
|
||||
|
||||
internal fun String.unescapeHtml(): String {
|
||||
return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
|
||||
}
|
@ -1687,6 +1687,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url))
|
||||
}
|
||||
|
||||
override fun onPreviewUrlImageClicked(sharedView: View?, mxcUrl: String?, title: String?) {
|
||||
navigator.openBigImageViewer(requireActivity(), sharedView, mxcUrl, title)
|
||||
}
|
||||
|
||||
private fun onShareActionClicked(action: EventSharedAction.Share) {
|
||||
if (action.messageContent is MessageTextContent) {
|
||||
shareText(requireContext(), action.messageContent.body)
|
||||
|
@ -87,12 +87,12 @@ import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
@ -754,8 +754,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
// is original event a reply?
|
||||
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
|
||||
if (inReplyTo != null) {
|
||||
// TODO check if same content?
|
||||
room.getTimeLineEvent(inReplyTo)?.let {
|
||||
|
@ -130,6 +130,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
interface PreviewUrlCallback {
|
||||
fun onPreviewUrlClicked(url: String)
|
||||
fun onPreviewUrlCloseClicked(eventId: String, url: String)
|
||||
fun onPreviewUrlImageClicked(sharedView: View?, mxcUrl: String?, title: String?)
|
||||
}
|
||||
|
||||
// Map eventId to adapter position
|
||||
|
@ -23,7 +23,7 @@ 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.UrlPreviewBinding
|
||||
import im.vector.app.databinding.ViewUrlPreviewBinding
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
|
||||
@ -39,7 +39,7 @@ class PreviewUrlView @JvmOverloads constructor(
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener {
|
||||
|
||||
private lateinit var views: UrlPreviewBinding
|
||||
private lateinit var views: ViewUrlPreviewBinding
|
||||
|
||||
var delegate: TimelineEventController.PreviewUrlCallback? = null
|
||||
|
||||
@ -80,6 +80,19 @@ class PreviewUrlView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -90,10 +103,11 @@ class PreviewUrlView @JvmOverloads constructor(
|
||||
// PRIVATE METHODS ****************************************************************************************************************************************
|
||||
|
||||
private fun setupView() {
|
||||
inflate(context, R.layout.url_preview, this)
|
||||
views = UrlPreviewBinding.bind(this)
|
||||
inflate(context, R.layout.view_url_preview, this)
|
||||
views = ViewUrlPreviewBinding.bind(this)
|
||||
|
||||
setOnClickListener(this)
|
||||
views.urlPreviewImage.setOnClickListener { onImageClick() }
|
||||
views.urlPreviewClose.setOnClickListener { onCloseClick() }
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,6 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -256,14 +255,13 @@ class DefaultNavigator @Inject constructor(
|
||||
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
|
||||
}
|
||||
|
||||
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
||||
matrixItem.avatarUrl
|
||||
override fun openBigImageViewer(activity: Activity, sharedElement: View?, mxcUrl: String?, title: String?) {
|
||||
mxcUrl
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let { avatarUrl ->
|
||||
val intent = BigImageViewerActivity.newIntent(activity, matrixItem.getBestName(), avatarUrl)
|
||||
val intent = BigImageViewerActivity.newIntent(activity, title, avatarUrl)
|
||||
val options = sharedElement?.let {
|
||||
ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it)
|
||||
?: "")
|
||||
ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it) ?: "")
|
||||
}
|
||||
activity.startActivity(intent, options?.toBundle())
|
||||
}
|
||||
|
@ -80,7 +80,11 @@ interface Navigator {
|
||||
|
||||
fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null)
|
||||
|
||||
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
|
||||
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
||||
openBigImageViewer(activity, sharedElement, matrixItem.avatarUrl, matrixItem.getBestName())
|
||||
}
|
||||
|
||||
fun openBigImageViewer(activity: Activity, sharedElement: View?, mxcUrl: String?, title: String?)
|
||||
|
||||
fun openPinCode(context: Context,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||
|
@ -25,9 +25,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
@ -52,7 +50,6 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.media.BigImageViewerActivity
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
@ -103,8 +100,8 @@ class RoomProfileFragment @Inject constructor(
|
||||
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(
|
||||
headerView,
|
||||
listOf(views.matrixProfileToolbarAvatarImageView,
|
||||
views.matrixProfileToolbarTitleView,
|
||||
views.matrixProfileDecorationToolbarAvatarImageView)
|
||||
views.matrixProfileToolbarTitleView,
|
||||
views.matrixProfileDecorationToolbarAvatarImageView)
|
||||
)
|
||||
views.matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
|
||||
roomProfileViewModel.observeViewEvents {
|
||||
@ -289,13 +286,7 @@ class RoomProfileFragment @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) = withState(roomProfileViewModel) {
|
||||
matrixItem.avatarUrl
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { avatarUrl ->
|
||||
val intent = BigImageViewerActivity.newIntent(requireContext(), matrixItem.getBestName(), avatarUrl)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, ViewCompat.getTransitionName(view) ?: "")
|
||||
startActivity(intent, options.toBundle())
|
||||
}
|
||||
private fun onAvatarClicked(view: View, matrixItem: MatrixItem.RoomItem) {
|
||||
navigator.openBigImageViewer(requireActivity(), view, matrixItem)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user