Update url preview when the event is edited.

This commit is contained in:
Onuray Sahin 2021-01-21 15:20:19 +03:00
parent 618d1f5de6
commit 5eeb545ae2
6 changed files with 37 additions and 23 deletions

View File

@ -10,6 +10,7 @@ Improvements 🙌:
Bugfix 🐛: Bugfix 🐛:
- Fix clear cache issue: sometimes, after a clear cache, there is still a token, so the init sync service is not started. - Fix clear cache issue: sometimes, after a clear cache, there is still a token, so the init sync service is not started.
- Sidebar too large in horizontal orientation or tablets (#475) - Sidebar too large in horizontal orientation or tablets (#475)
- UrlPreview should be updated when the url is edited and changed (#2678)
Translations 🗣: Translations 🗣:
- -

View File

@ -17,15 +17,17 @@
package org.matrix.android.sdk.api.session.media package org.matrix.android.sdk.api.session.media
import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.cache.CacheStrategy
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
interface MediaService { interface MediaService {
/** /**
* Extract URLs from an Event. * Extract URLs from a TimelineEvent.
* @return the list of URLs contains in the body of the Event. It does not mean that URLs in this list have UrlPreview data * @param event TimelineEvent to extract the URL from.
* @param forceExtract Should be used for edited events. If true, URL will be extracted again even it is already in the cache.
* @return the list of URLs contains in the body of the TimelineEvent. It does not mean that URLs in this list have UrlPreview data
*/ */
fun extractUrls(event: Event): List<String> fun extractUrls(event: TimelineEvent, forceExtract: Boolean = false): List<String>
/** /**
* Get Raw Url Preview data from the homeserver. There is no cache management for this request * Get Raw Url Preview data from the homeserver. There is no cache management for this request

View File

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.cache.CacheStrategy
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.media.PreviewUrlData import org.matrix.android.sdk.api.session.media.PreviewUrlData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.util.getOrPut import org.matrix.android.sdk.internal.util.getOrPut
import javax.inject.Inject import javax.inject.Inject
@ -34,8 +35,9 @@ internal class DefaultMediaService @Inject constructor(
// Cache of extracted URLs // Cache of extracted URLs
private val extractedUrlsCache = LruCache<String, List<String>>(1_000) private val extractedUrlsCache = LruCache<String, List<String>>(1_000)
override fun extractUrls(event: Event): List<String> { override fun extractUrls(event: TimelineEvent, forceExtract: Boolean): List<String> {
return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) } if (forceExtract) extractedUrlsCache.remove(event.root.cacheKey())
return extractedUrlsCache.getOrPut(event.root.cacheKey()) { urlsExtractor.extract(event) }
} }
private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}" private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}"

View File

@ -17,21 +17,19 @@
package org.matrix.android.sdk.internal.session.media package org.matrix.android.sdk.internal.session.media
import android.util.Patterns import android.util.Patterns
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType 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 javax.inject.Inject import javax.inject.Inject
internal class UrlsExtractor @Inject constructor() { internal class UrlsExtractor @Inject constructor() {
// Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later // Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later
private val urlRegex = Patterns.WEB_URL.toRegex() private val urlRegex = Patterns.WEB_URL.toRegex()
fun extract(event: Event): List<String> { fun extract(event: TimelineEvent): List<String> {
return event.takeIf { it.getClearType() == EventType.MESSAGE } return event.takeIf { it.root.getClearType() == EventType.MESSAGE }
?.getClearContent() ?.getLastMessageContent()
?.toModel<MessageContent>()
?.takeIf { ?.takeIf {
it.msgType == MessageType.MSGTYPE_TEXT it.msgType == MessageType.MSGTYPE_TEXT
|| it.msgType == MessageType.MSGTYPE_NOTICE || it.msgType == MessageType.MSGTYPE_NOTICE

View File

@ -1396,7 +1396,7 @@ class RoomDetailViewModel @AssistedInject constructor(
snapshot snapshot
.takeIf { state.asyncRoomSummary.invoke()?.isEncrypted == false } .takeIf { state.asyncRoomSummary.invoke()?.isEncrypted == false }
?.forEach { ?.forEach {
previewUrlRetriever.getPreviewUrl(it.root, viewModelScope) previewUrlRetriever.getPreviewUrl(it, viewModelScope)
} }
} }
} }

View File

@ -16,37 +16,46 @@
package im.vector.app.features.home.room.detail.timeline.url package im.vector.app.features.home.room.detail.timeline.url
import android.os.Handler
import android.os.Looper
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.cache.CacheStrategy
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
class PreviewUrlRetriever(session: Session) { class PreviewUrlRetriever(session: Session) {
private val mediaService = session.mediaService() private val mediaService = session.mediaService()
private val data = mutableMapOf<String, PreviewUrlUiState>() private val data = mutableMapOf<String, PreviewUrlUiState>()
private val listeners = mutableMapOf<String, MutableSet<PreviewUrlRetrieverListener>>() private val listeners = mutableMapOf<String, MutableSet<PreviewUrlRetrieverListener>>()
private val uiHandler = Handler(Looper.getMainLooper())
// In memory list // In memory list
private val blockedUrl = mutableSetOf<String>() private val blockedUrl = mutableSetOf<String>()
fun getPreviewUrl(event: Event, coroutineScope: CoroutineScope) { fun getPreviewUrl(event: TimelineEvent, coroutineScope: CoroutineScope) {
val eventId = event.eventId ?: return val eventId = event.root.eventId ?: return
synchronized(data) { synchronized(data) {
if (data[eventId] == null) { val isEditedEvent = event.hasBeenEdited()
if (data[eventId] == null || isEditedEvent) {
// Keep only the first URL for the moment // Keep only the first URL for the moment
val url = mediaService.extractUrls(event) val url = mediaService.extractUrls(event, forceExtract = isEditedEvent)
.firstOrNull() .firstOrNull()
?.takeIf { it !in blockedUrl } ?.takeIf { it !in blockedUrl }
if (url == null) { if (url == null) {
updateState(eventId, PreviewUrlUiState.NoUrl) updateState(eventId, PreviewUrlUiState.NoUrl)
} else { url
} else if (url != (data[eventId] as? PreviewUrlUiState.Data)?.url) {
updateState(eventId, PreviewUrlUiState.Loading) updateState(eventId, PreviewUrlUiState.Loading)
url
} else {
// Already handled
null
} }
url
} else { } else {
// Already handled // Already handled
null null
@ -96,8 +105,10 @@ class PreviewUrlRetriever(session: Session) {
private fun updateState(eventId: String, state: PreviewUrlUiState) { private fun updateState(eventId: String, state: PreviewUrlUiState) {
data[eventId] = state data[eventId] = state
// Notify the listener // Notify the listener
listeners[eventId].orEmpty().forEach { uiHandler.post {
it.onStateUpdated(state) listeners[eventId].orEmpty().forEach {
it.onStateUpdated(state)
}
} }
} }