Pillify permalinks (#8242)
This commit is contained in:
parent
b85a06422c
commit
9fd1a22e10
|
@ -0,0 +1 @@
|
||||||
|
Permalinks to a room/space are pillified
|
|
@ -0,0 +1 @@
|
||||||
|
Permalinks to a matrix user are pillified
|
|
@ -0,0 +1 @@
|
||||||
|
Permalinks to messages are pillified
|
|
@ -3539,4 +3539,11 @@
|
||||||
|
|
||||||
<string name="settings_access_token">Access Token</string>
|
<string name="settings_access_token">Access Token</string>
|
||||||
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
|
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
|
||||||
|
|
||||||
|
<!-- Pills -->
|
||||||
|
<string name="pill_message_from_user">Message from %s</string>
|
||||||
|
<string name="pill_message_from_unknown_user">Message</string>
|
||||||
|
<string name="pill_message_in_room">Message in %s</string>
|
||||||
|
<string name="pill_message_in_unknown_room">Message in room</string>
|
||||||
|
<string name="pill_message_unknown_room_or_space">Room/Space</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
<item name="dialog_width_ratio" format="float" type="dimen">0.75</item>
|
<item name="dialog_width_ratio" format="float" type="dimen">0.75</item>
|
||||||
|
|
||||||
<dimen name="pill_avatar_size">16dp</dimen>
|
<dimen name="pill_avatar_size">20sp</dimen>
|
||||||
<dimen name="pill_min_height">20dp</dimen>
|
<dimen name="pill_min_height">26sp</dimen>
|
||||||
<dimen name="pill_text_padding">4dp</dimen>
|
<dimen name="pill_text_padding">4dp</dimen>
|
||||||
|
|
||||||
<dimen name="call_pip_height">128dp</dimen>
|
<dimen name="call_pip_height">128dp</dimen>
|
||||||
|
|
|
@ -65,27 +65,14 @@ object MatrixPatterns {
|
||||||
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
|
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
|
||||||
const val SEP_REGEX = "/"
|
const val SEP_REGEX = "/"
|
||||||
|
|
||||||
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK = PERMALINK_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
private val PATTERN_CONTAIN_APP_PERMALINK = APP_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
|
||||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
|
||||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
|
||||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
// ascii characters in the range \x20 (space) to \x7E (~)
|
// ascii characters in the range \x20 (space) to \x7E (~)
|
||||||
val ORDER_STRING_REGEX = "[ -~]+".toRegex()
|
val ORDER_STRING_REGEX = "[ -~]+".toRegex()
|
||||||
|
|
||||||
// list of patterns to find some matrix item.
|
// list of patterns to find some matrix item.
|
||||||
val MATRIX_PATTERNS = listOf(
|
val MATRIX_PATTERNS = listOf(
|
||||||
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID,
|
|
||||||
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS,
|
|
||||||
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID,
|
|
||||||
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS,
|
|
||||||
PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER,
|
PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER,
|
||||||
PATTERN_CONTAIN_MATRIX_ALIAS,
|
PATTERN_CONTAIN_MATRIX_ALIAS,
|
||||||
PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER,
|
PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER,
|
||||||
|
@ -146,6 +133,12 @@ object MatrixPatterns {
|
||||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPermalink(str: String?): Boolean {
|
||||||
|
return str != null &&
|
||||||
|
(PATTERN_CONTAIN_MATRIX_TO_PERMALINK.containsMatchIn(str) ||
|
||||||
|
PATTERN_CONTAIN_APP_PERMALINK.containsMatchIn(str))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract server name from a matrix id.
|
* Extract server name from a matrix id.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.permalinks
|
package org.matrix.android.sdk.api.session.permalinks
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
|
import android.util.Patterns
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,22 +45,26 @@ object MatrixLinkify {
|
||||||
}
|
}
|
||||||
val text = spannable.toString()
|
val text = spannable.toString()
|
||||||
var hasMatch = false
|
var hasMatch = false
|
||||||
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
|
for (pattern in listOf(Patterns.WEB_URL.toRegex()).plus(MatrixPatterns.MATRIX_PATTERNS)) {
|
||||||
for (match in pattern.findAll(spannable)) {
|
for (match in pattern.findAll(spannable)) {
|
||||||
hasMatch = true
|
hasMatch = true
|
||||||
val startPos = match.range.first
|
val startPos = match.range.first
|
||||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
val endPos = match.range.last + 1
|
val endPos = match.range.last + 1
|
||||||
var url = text.substring(match.range)
|
var url = text.substring(match.range)
|
||||||
if (MatrixPatterns.isUserId(url) ||
|
val isPermalink = MatrixPatterns.isPermalink(url)
|
||||||
|
if (isPermalink ||
|
||||||
|
MatrixPatterns.isUserId(url) ||
|
||||||
MatrixPatterns.isRoomAlias(url) ||
|
MatrixPatterns.isRoomAlias(url) ||
|
||||||
MatrixPatterns.isRoomId(url) ||
|
MatrixPatterns.isRoomId(url) ||
|
||||||
MatrixPatterns.isGroupId(url) ||
|
MatrixPatterns.isGroupId(url) ||
|
||||||
MatrixPatterns.isEventId(url)) {
|
MatrixPatterns.isEventId(url)) {
|
||||||
url = PermalinkService.MATRIX_TO_URL_BASE + url
|
if (!isPermalink) {
|
||||||
|
url = PermalinkService.MATRIX_TO_URL_BASE + url
|
||||||
|
}
|
||||||
|
val span = MatrixPermalinkSpan(url, callback)
|
||||||
|
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
val span = MatrixPermalinkSpan(url, callback)
|
|
||||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,8 @@ sealed class MatrixItem(
|
||||||
data class RoomItem(
|
data class RoomItem(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val displayName: String? = null,
|
override val displayName: String? = null,
|
||||||
override val avatarUrl: String? = null
|
override val avatarUrl: String? = null,
|
||||||
|
val roomDisplayName: String? = null
|
||||||
) :
|
) :
|
||||||
MatrixItem(id, displayName, avatarUrl) {
|
MatrixItem(id, displayName, avatarUrl) {
|
||||||
init {
|
init {
|
||||||
|
@ -102,7 +103,8 @@ sealed class MatrixItem(
|
||||||
data class RoomAliasItem(
|
data class RoomAliasItem(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val displayName: String? = null,
|
override val displayName: String? = null,
|
||||||
override val avatarUrl: String? = null
|
override val avatarUrl: String? = null,
|
||||||
|
val roomDisplayName: String? = null
|
||||||
) :
|
) :
|
||||||
MatrixItem(id, displayName, avatarUrl) {
|
MatrixItem(id, displayName, avatarUrl) {
|
||||||
init {
|
init {
|
||||||
|
@ -136,6 +138,8 @@ sealed class MatrixItem(
|
||||||
val displayName = when (this) {
|
val displayName = when (this) {
|
||||||
// use the room display name for the notify everyone item
|
// use the room display name for the notify everyone item
|
||||||
is EveryoneInRoomItem -> roomDisplayName
|
is EveryoneInRoomItem -> roomDisplayName
|
||||||
|
is RoomItem -> roomDisplayName ?: displayName
|
||||||
|
is RoomAliasItem -> roomDisplayName ?: displayName
|
||||||
else -> displayName
|
else -> displayName
|
||||||
}
|
}
|
||||||
return (displayName?.takeIf { it.isNotBlank() } ?: id)
|
return (displayName?.takeIf { it.isNotBlank() } ?: id)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
fun MatrixItem.getBestName(): String {
|
fun MatrixItem.getBestName(): String {
|
||||||
// Note: this code is copied from [DisplayNameResolver] in the SDK
|
// Note: this code is copied from [DisplayNameResolver] in the SDK
|
||||||
return if (this is MatrixItem.RoomAliasItem) {
|
return if (this is MatrixItem.RoomAliasItem && displayName.isNullOrBlank()) {
|
||||||
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
// Best name is the id, and we keep the displayName of the room for the case we need the first letter
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -163,7 +163,6 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||||
import im.vector.app.features.home.room.threads.ThreadsManager
|
import im.vector.app.features.home.room.threads.ThreadsManager
|
||||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
import im.vector.app.features.html.PillsPostProcessor
|
|
||||||
import im.vector.app.features.invite.VectorInviteView
|
import im.vector.app.features.invite.VectorInviteView
|
||||||
import im.vector.app.features.location.LocationSharingMode
|
import im.vector.app.features.location.LocationSharingMode
|
||||||
import im.vector.app.features.location.toLocationData
|
import im.vector.app.features.location.toLocationData
|
||||||
|
@ -247,7 +246,6 @@ class TimelineFragment :
|
||||||
@Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider
|
@Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider
|
||||||
@Inject lateinit var imageContentRenderer: ImageContentRenderer
|
@Inject lateinit var imageContentRenderer: ImageContentRenderer
|
||||||
@Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
|
@Inject lateinit var roomDetailPendingActionStore: RoomDetailPendingActionStore
|
||||||
@Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory
|
|
||||||
@Inject lateinit var callManager: WebRtcCallManager
|
@Inject lateinit var callManager: WebRtcCallManager
|
||||||
@Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker
|
@Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker
|
||||||
@Inject lateinit var shareIntentHandler: ShareIntentHandler
|
@Inject lateinit var shareIntentHandler: ShareIntentHandler
|
||||||
|
|
|
@ -20,21 +20,30 @@ import android.content.Context
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
import android.util.Patterns
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.html.PillImageSpan
|
import im.vector.app.features.html.PillImageSpan
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||||
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||||
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
class EventTextRenderer @AssistedInject constructor(
|
class EventTextRenderer @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String?,
|
@Assisted private val roomId: String?,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -46,7 +55,8 @@ class EventTextRenderer @AssistedInject constructor(
|
||||||
* @param text the text to be rendered
|
* @param text the text to be rendered
|
||||||
*/
|
*/
|
||||||
fun render(text: CharSequence): CharSequence {
|
fun render(text: CharSequence): CharSequence {
|
||||||
return renderNotifyEveryone(text)
|
val formattedText = renderPermalinks(text)
|
||||||
|
return renderNotifyEveryone(formattedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderNotifyEveryone(text: CharSequence): CharSequence {
|
private fun renderNotifyEveryone(text: CharSequence): CharSequence {
|
||||||
|
@ -59,8 +69,18 @@ class EventTextRenderer @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderPermalinks(text: CharSequence): CharSequence {
|
||||||
|
return if (roomId != null) {
|
||||||
|
SpannableStringBuilder(text).apply {
|
||||||
|
addPermalinksSpans(this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
|
private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
|
||||||
val room: RoomSummary? = activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
|
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
|
||||||
val matrixItem = MatrixItem.EveryoneInRoomItem(
|
val matrixItem = MatrixItem.EveryoneInRoomItem(
|
||||||
id = roomId,
|
id = roomId,
|
||||||
avatarUrl = room?.avatarUrl,
|
avatarUrl = room?.avatarUrl,
|
||||||
|
@ -76,6 +96,23 @@ class EventTextRenderer @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addPermalinksSpans(text: Spannable) {
|
||||||
|
for (match in Patterns.WEB_URL.toRegex().findAll(text)) {
|
||||||
|
val url = text.substring(match.range)
|
||||||
|
val matrixItem = if (MatrixPatterns.isPermalink(url)) {
|
||||||
|
when (val permalinkData = PermalinkParser.parse(url)) {
|
||||||
|
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
|
||||||
|
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (matrixItem != null) {
|
||||||
|
addPillSpan(text, createPillImageSpan(matrixItem), match.range.first, match.range.last + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createPillImageSpan(matrixItem: MatrixItem) =
|
private fun createPillImageSpan(matrixItem: MatrixItem) =
|
||||||
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
||||||
|
|
||||||
|
@ -87,4 +124,46 @@ class EventTextRenderer @AssistedInject constructor(
|
||||||
) {
|
) {
|
||||||
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =
|
||||||
|
roomId?.let { sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, it)?.toMatrixItem() }
|
||||||
|
?: sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem()
|
||||||
|
|
||||||
|
private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem =
|
||||||
|
if (eventId.isNullOrEmpty()) {
|
||||||
|
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias)
|
||||||
|
when {
|
||||||
|
isRoomAlias -> MatrixItem.RoomAliasItem(roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
||||||
|
room == null -> MatrixItem.RoomItem(roomIdOrAlias, context.getString(R.string.pill_message_unknown_room_or_space))
|
||||||
|
room.roomType == RoomType.SPACE -> MatrixItem.SpaceItem(roomIdOrAlias, room.displayName, room.avatarUrl)
|
||||||
|
else -> MatrixItem.RoomItem(roomIdOrAlias, room.displayName, room.avatarUrl)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (roomIdOrAlias == roomId) {
|
||||||
|
val session = sessionHolder.getSafeActiveSession()
|
||||||
|
val event = session?.eventService()?.getEventFromCache(roomId, eventId!!)
|
||||||
|
val user = event?.senderId?.let { session.roomService().getRoomMember(it, roomId) }
|
||||||
|
val text = user?.let {
|
||||||
|
context.getString(R.string.pill_message_from_user, user.displayName)
|
||||||
|
} ?: context.getString(R.string.pill_message_from_unknown_user)
|
||||||
|
MatrixItem.RoomItem(roomIdOrAlias, text, user?.avatarUrl, user?.displayName)
|
||||||
|
} else {
|
||||||
|
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias)
|
||||||
|
when {
|
||||||
|
isRoomAlias -> MatrixItem.RoomAliasItem(
|
||||||
|
roomIdOrAlias,
|
||||||
|
context.getString(R.string.pill_message_in_room, room?.displayName ?: roomIdOrAlias),
|
||||||
|
room?.avatarUrl,
|
||||||
|
room?.displayName
|
||||||
|
)
|
||||||
|
room != null -> MatrixItem.RoomItem(
|
||||||
|
roomIdOrAlias,
|
||||||
|
context.getString(R.string.pill_message_in_room, room.displayName),
|
||||||
|
room.avatarUrl,
|
||||||
|
room.displayName
|
||||||
|
)
|
||||||
|
else -> MatrixItem.RoomItem(roomIdOrAlias, context.getString(R.string.pill_message_in_unknown_room))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,11 @@ fun CharSequence.findPillsAndProcess(scope: CoroutineScope, processBlock: (PillI
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CharSequence.linkify(callback: TimelineEventController.UrlClickCallback?): CharSequence {
|
fun CharSequence.linkify(callback: TimelineEventController.UrlClickCallback?): CharSequence {
|
||||||
val text = this.toString()
|
|
||||||
// SpannableStringBuilder is used to avoid Epoxy throwing ImmutableModelException
|
// SpannableStringBuilder is used to avoid Epoxy throwing ImmutableModelException
|
||||||
val spannable = SpannableStringBuilder(this)
|
val spannable = SpannableStringBuilder(this)
|
||||||
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
||||||
override fun onUrlClicked(url: String) {
|
override fun onUrlClicked(url: String) {
|
||||||
callback?.onUrlClicked(url, text)
|
callback?.onUrlClicked(url, this.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
VectorLinkify.addLinks(spannable, true)
|
VectorLinkify.addLinks(spannable, true)
|
||||||
|
|
|
@ -26,14 +26,17 @@ import android.graphics.drawable.Drawable
|
||||||
import android.text.style.ReplacementSpan
|
import android.text.style.ReplacementSpan
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.bumptech.glide.request.target.SimpleTarget
|
import com.bumptech.glide.request.target.SimpleTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.chip.ChipDrawable
|
import com.google.android.material.chip.ChipDrawable
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.isMatrixId
|
||||||
import im.vector.app.core.glide.GlideRequests
|
import im.vector.app.core.glide.GlideRequests
|
||||||
import im.vector.app.features.displayname.getBestName
|
import im.vector.app.features.displayname.getBestName
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
|
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
@ -111,10 +114,28 @@ class PillImageSpan(
|
||||||
|
|
||||||
private fun createChipDrawable(): ChipDrawable {
|
private fun createChipDrawable(): ChipDrawable {
|
||||||
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
|
val textPadding = context.resources.getDimension(R.dimen.pill_text_padding)
|
||||||
val icon = try {
|
val icon = when {
|
||||||
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
matrixItem is MatrixItem.RoomAliasItem && matrixItem.avatarUrl.isNullOrEmpty() &&
|
||||||
} catch (exception: Exception) {
|
matrixItem.displayName == context.getString(R.string.pill_message_in_room, matrixItem.id) -> {
|
||||||
avatarRenderer.getPlaceholderDrawable(matrixItem)
|
ContextCompat.getDrawable(context, R.drawable.ic_permalink_round)
|
||||||
|
}
|
||||||
|
matrixItem is MatrixItem.RoomItem && matrixItem.avatarUrl.isNullOrEmpty() && (
|
||||||
|
matrixItem.displayName == context.getString(R.string.pill_message_in_unknown_room) ||
|
||||||
|
matrixItem.displayName == context.getString(R.string.pill_message_unknown_room_or_space) ||
|
||||||
|
matrixItem.displayName == context.getString(R.string.pill_message_from_unknown_user)
|
||||||
|
) -> {
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_permalink_round)
|
||||||
|
}
|
||||||
|
matrixItem is MatrixItem.UserItem && matrixItem.avatarUrl.isNullOrEmpty() && matrixItem.displayName?.isMatrixId().orTrue() -> {
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_user_round)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
try {
|
||||||
|
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
avatarRenderer.getPlaceholderDrawable(matrixItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import io.noties.markwon.core.spans.LinkSpan
|
import io.noties.markwon.core.spans.LinkSpan
|
||||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
import org.matrix.android.sdk.api.session.getUser
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
@ -56,15 +56,15 @@ class PillsPostProcessor @AssistedInject constructor(
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun afterRender(renderedText: Spannable) {
|
override fun afterRender(renderedText: Spannable) {
|
||||||
addPillSpans(renderedText, roomId)
|
addPillSpans(renderedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Helper methods
|
* Helper methods
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
private fun addPillSpans(renderedText: Spannable, roomId: String?) {
|
private fun addPillSpans(renderedText: Spannable) {
|
||||||
addLinkSpans(renderedText, roomId)
|
addLinkSpans(renderedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addPillSpan(
|
private fun addPillSpan(
|
||||||
|
@ -76,11 +76,11 @@ class PillsPostProcessor @AssistedInject constructor(
|
||||||
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addLinkSpans(renderedText: Spannable, roomId: String?) {
|
private fun addLinkSpans(renderedText: Spannable) {
|
||||||
// We let markdown handle links and then we add PillImageSpan if needed.
|
// We let markdown handle links and then we add PillImageSpan if needed.
|
||||||
val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
|
val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
|
||||||
linkSpans.forEach { linkSpan ->
|
linkSpans.forEach { linkSpan ->
|
||||||
val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach
|
val pillSpan = linkSpan.createPillSpan() ?: return@forEach
|
||||||
val startSpan = renderedText.getSpanStart(linkSpan)
|
val startSpan = renderedText.getSpanStart(linkSpan)
|
||||||
val endSpan = renderedText.getSpanEnd(linkSpan)
|
val endSpan = renderedText.getSpanEnd(linkSpan)
|
||||||
// GlideImagesPlugin causes duplicated pills if we have a nested spans in the pill span,
|
// GlideImagesPlugin causes duplicated pills if we have a nested spans in the pill span,
|
||||||
|
@ -104,21 +104,18 @@ class PillsPostProcessor @AssistedInject constructor(
|
||||||
private fun createPillImageSpan(matrixItem: MatrixItem) =
|
private fun createPillImageSpan(matrixItem: MatrixItem) =
|
||||||
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
||||||
|
|
||||||
private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
|
private fun LinkSpan.createPillSpan(): PillImageSpan? {
|
||||||
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
|
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
|
||||||
is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId)
|
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
|
||||||
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
|
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return null
|
} ?: return null
|
||||||
return createPillImageSpan(matrixItem)
|
return createPillImageSpan(matrixItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? =
|
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =
|
||||||
if (roomId == null) {
|
roomId?.let { sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, it)?.toMatrixItem() }
|
||||||
sessionHolder.getSafeActiveSession()?.getUserOrDefault(userId)?.toMatrixItem()
|
?: sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem()
|
||||||
} else {
|
|
||||||
sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, roomId)?.toMatrixItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? =
|
private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? =
|
||||||
if (eventId == null) {
|
if (eventId == null) {
|
||||||
|
|
|
@ -110,13 +110,8 @@ class PermalinkHandler @Inject constructor(
|
||||||
|
|
||||||
val rootThreadEventId = permalinkData.eventId?.let { eventId ->
|
val rootThreadEventId = permalinkData.eventId?.let { eventId ->
|
||||||
val room = roomId?.let { session?.getRoom(it) }
|
val room = roomId?.let { session?.getRoom(it) }
|
||||||
|
val event = room?.getTimelineEvent(eventId)
|
||||||
val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId()
|
event?.root?.getRootThreadEventId() ?: eventId.takeIf { event?.isRootThread() == true }
|
||||||
rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) {
|
|
||||||
eventId
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
openRoom(
|
openRoom(
|
||||||
navigationInterceptor,
|
navigationInterceptor,
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,12m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
|
||||||
|
android:fillColor="@color/element_name_01"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m12.378,8.101 l0.356,-0.356c0.984,-0.984 2.57,-0.994 3.543,-0.021 0.973,0.972 0.963,2.559 -0.021,3.543l-1.693,1.693c-0.984,0.984 -2.57,0.994 -3.543,0.021m0.603,2.919 l-0.356,0.356c-0.984,0.984 -2.57,0.994 -3.543,0.021 -0.973,-0.973 -0.963,-2.559 0.021,-3.543l1.693,-1.693c0.984,-0.984 2.57,-0.994 3.543,-0.021"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="@color/palette_white"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
|
@ -4,15 +4,15 @@
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:pathData="M17.5911,20.2922C15.9951,21.3704 14.0711,22 12,22C9.7488,22 7.6713,21.2561 6,20.0007C3.5711,18.1763 2,15.2716 2,12C2,6.4771 6.4771,2 12,2C17.5228,2 22,6.4771 22,12C22,15.4518 20.2511,18.4951 17.5911,20.2922ZM12,12.5C13.6569,12.5 15,11.0449 15,9.25C15,7.4551 13.6569,6 12,6C10.3431,6 9,7.4551 9,9.25C9,11.0449 10.3431,12.5 12,12.5ZM12,20C14.162,20 16.1236,19.1424 17.5634,17.7488C16.673,15.5506 14.5176,14 12,14C9.4824,14 7.327,15.5506 6.4366,17.7488C7.8763,19.1424 9.838,20 12,20Z"
|
android:pathData="M18.709,21.951C16.794,23.244 14.485,24 12,24C9.299,24 6.806,23.107 4.8,21.601C1.885,19.412 0,15.926 0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,16.142 21.901,19.794 18.709,21.951ZM12,12.6C13.988,12.6 15.6,10.854 15.6,8.7C15.6,6.546 13.988,4.8 12,4.8C10.012,4.8 8.4,6.546 8.4,8.7C8.4,10.854 10.012,12.6 12,12.6ZM12,21.6C14.594,21.6 16.948,20.571 18.676,18.899C17.608,16.261 15.021,14.4 12,14.4C8.979,14.4 6.392,16.261 5.324,18.899C7.052,20.571 9.406,21.6 12,21.6Z"
|
||||||
android:fillColor="#C1C6CD"
|
android:fillColor="?vctr_content_secondary"
|
||||||
android:fillType="evenOdd"/>
|
android:fillType="evenOdd"/>
|
||||||
<group>
|
<group>
|
||||||
<clip-path
|
<clip-path
|
||||||
android:pathData="M17.5911,20.2922C15.9951,21.3704 14.0711,22 12,22C9.7488,22 7.6713,21.2561 6,20.0007C3.5711,18.1763 2,15.2716 2,12C2,6.4771 6.4771,2 12,2C17.5228,2 22,6.4771 22,12C22,15.4518 20.2511,18.4951 17.5911,20.2922ZM12,12.5C13.6569,12.5 15,11.0449 15,9.25C15,7.4551 13.6569,6 12,6C10.3431,6 9,7.4551 9,9.25C9,11.0449 10.3431,12.5 12,12.5ZM12,20C14.162,20 16.1236,19.1424 17.5634,17.7488C16.673,15.5506 14.5176,14 12,14C9.4824,14 7.327,15.5506 6.4366,17.7488C7.8763,19.1424 9.838,20 12,20Z"
|
android:pathData="M18.709,21.951C16.794,23.244 14.485,24 12,24C9.299,24 6.806,23.107 4.8,21.601C1.885,19.412 0,15.926 0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,16.142 21.901,19.794 18.709,21.951ZM12,12.6C13.988,12.6 15.6,10.854 15.6,8.7C15.6,6.546 13.988,4.8 12,4.8C10.012,4.8 8.4,6.546 8.4,8.7C8.4,10.854 10.012,12.6 12,12.6ZM12,21.6C14.594,21.6 16.948,20.571 18.676,18.899C17.608,16.261 15.021,14.4 12,14.4C8.979,14.4 6.392,16.261 5.324,18.899C7.052,20.571 9.406,21.6 12,21.6Z"
|
||||||
android:fillType="evenOdd"/>
|
android:fillType="evenOdd"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M17.5911,20.2922L16.4715,18.6349L17.5911,20.2922ZM6,20.0007L4.7989,21.5999L4.7989,21.5999L6,20.0007ZM17.5634,17.7488L18.9544,19.1859L19.9234,18.2479L19.4171,16.998L17.5634,17.7488ZM6.4366,17.7488L4.5829,16.998L4.0766,18.2479L5.0456,19.1859L6.4366,17.7488ZM12,24C14.4825,24 16.7945,23.244 18.7107,21.9494L16.4715,18.6349C15.1957,19.4968 13.6596,20 12,20V24ZM4.7989,21.5999C6.8046,23.1065 9.3008,24 12,24V20C10.1967,20 8.538,19.4058 7.2011,18.4016L4.7989,21.5999ZM0,12C0,15.9273 1.8887,19.414 4.7989,21.5999L7.2011,18.4016C5.2535,16.9387 4,14.616 4,12H0ZM12,0C5.3726,0 0,5.3726 0,12H4C4,7.5817 7.5817,4 12,4V0ZM24,12C24,5.3726 18.6274,0 12,0V4C16.4183,4 20,7.5817 20,12H24ZM18.7107,21.9494C21.8977,19.7963 24,16.144 24,12H20C20,14.7596 18.6045,17.1939 16.4715,18.6349L18.7107,21.9494ZM13,9.25C13,10.0941 12.4046,10.5 12,10.5V14.5C14.9091,14.5 17,11.9958 17,9.25H13ZM12,8C12.4046,8 13,8.4059 13,9.25H17C17,6.5043 14.9091,4 12,4V8ZM11,9.25C11,8.4059 11.5954,8 12,8V4C9.0909,4 7,6.5043 7,9.25H11ZM12,10.5C11.5954,10.5 11,10.0941 11,9.25H7C7,11.9958 9.0909,14.5 12,14.5V10.5ZM16.1724,16.3118C15.0906,17.3588 13.6223,18 12,18V22C14.7017,22 17.1567,20.926 18.9544,19.1859L16.1724,16.3118ZM12,16C13.6752,16 15.1146,17.0305 15.7097,18.4996L19.4171,16.998C18.2314,14.0707 15.3599,12 12,12V16ZM8.2903,18.4996C8.8854,17.0305 10.3248,16 12,16V12C8.6401,12 5.7686,14.0707 4.5829,16.998L8.2903,18.4996ZM12,18C10.3777,18 8.9094,17.3588 7.8276,16.3118L5.0456,19.1859C6.8433,20.926 9.2983,22 12,22V18Z"
|
android:pathData="M18.709,21.951L19.564,23.216L18.709,21.951ZM4.8,21.601L3.883,22.822H3.883L4.8,21.601ZM18.676,18.899L19.738,19.996L20.478,19.28L20.092,18.325L18.676,18.899ZM5.324,18.899L3.908,18.325L3.522,19.28L4.262,19.996L5.324,18.899ZM12,25.527C14.8,25.527 17.404,24.675 19.564,23.216L17.854,20.685C16.184,21.814 14.171,22.473 12,22.473V25.527ZM3.883,22.822C6.144,24.52 8.956,25.527 12,25.527V22.473C9.641,22.473 7.467,21.694 5.717,20.38L3.883,22.822ZM-1.527,12C-1.527,16.427 0.601,20.357 3.883,22.822L5.717,20.38C3.17,18.466 1.527,15.425 1.527,12H-1.527ZM12,-1.527C4.529,-1.527 -1.527,4.529 -1.527,12H1.527C1.527,6.216 6.216,1.527 12,1.527V-1.527ZM25.527,12C25.527,4.529 19.471,-1.527 12,-1.527V1.527C17.784,1.527 22.473,6.216 22.473,12H25.527ZM19.564,23.216C23.159,20.788 25.527,16.671 25.527,12H22.473C22.473,15.613 20.644,18.8 17.854,20.685L19.564,23.216ZM14.073,8.7C14.073,10.128 13.032,11.073 12,11.073V14.127C14.944,14.127 17.127,11.58 17.127,8.7H14.073ZM12,6.327C13.032,6.327 14.073,7.272 14.073,8.7H17.127C17.127,5.82 14.944,3.273 12,3.273V6.327ZM9.927,8.7C9.927,7.272 10.968,6.327 12,6.327V3.273C9.055,3.273 6.873,5.82 6.873,8.7H9.927ZM12,11.073C10.968,11.073 9.927,10.128 9.927,8.7H6.873C6.873,11.58 9.055,14.127 12,14.127V11.073ZM17.614,17.801C16.16,19.209 14.182,20.073 12,20.073V23.127C15.007,23.127 17.737,21.933 19.738,19.996L17.614,17.801ZM12,15.927C14.378,15.927 16.417,17.391 17.26,19.472L20.092,18.325C18.798,15.131 15.664,12.873 12,12.873V15.927ZM6.74,19.472C7.582,17.391 9.622,15.927 12,15.927V12.873C8.336,12.873 5.202,15.131 3.908,18.325L6.74,19.472ZM12,20.073C9.818,20.073 7.84,19.209 6.386,17.801L4.262,19.996C6.263,21.933 8.993,23.127 12,23.127V20.073Z"
|
||||||
android:fillColor="#C1C6CD"/>
|
android:fillColor="?vctr_content_secondary"/>
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
Loading…
Reference in New Issue