diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixPatterns.kt new file mode 100644 index 00000000..1140b5df --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixPatterns.kt @@ -0,0 +1,115 @@ +package im.vector.matrix.android.api.permalinks + +import java.util.* +import java.util.regex.Pattern + +/** + * This class contains pattern to match the different Matrix ids + */ +object MatrixPatterns { + + // Note: TLD is not mandatory (localhost, IP address...) + private val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" + + // regex pattern to find matrix user ids in a string. + // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids + private val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" + val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + + // regex pattern to find room ids in a string. + private val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" + val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + + // regex pattern to find room aliases in a string. + private val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX" + val PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE) + + // regex pattern to find message ids in a string. + private val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX" + val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + + // regex pattern to find group ids in a string. + private val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX" + val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + + // regex pattern to find permalink with message id. + // Android does not support in URL so extract it. + private val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/" + private val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/" + private val SEP_REGEX = "/" + + private val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX + val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE) + + private val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX + val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE) + + private val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX + val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE) + + private val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX + val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE) + + // list of patterns to find some matrix item. + val MATRIX_PATTERNS = Arrays.asList( + MatrixPatterns.PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS, + MatrixPatterns.PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID, + MatrixPatterns.PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_ALIAS, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER, + MatrixPatterns.PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER + ) + + /** + * Tells if a string is a valid user Id. + * + * @param str the string to test + * @return true if the string is a valid user id + */ + fun isUserId(str: String?): Boolean { + return str != null && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches() + } + + /** + * Tells if a string is a valid room id. + * + * @param str the string to test + * @return true if the string is a valid room Id + */ + fun isRoomId(str: String?): Boolean { + return str != null && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches() + } + + /** + * Tells if a string is a valid room alias. + * + * @param str the string to test + * @return true if the string is a valid room alias. + */ + fun isRoomAlias(str: String?): Boolean { + return str != null && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches() + } + + /** + * Tells if a string is a valid event id. + * + * @param str the string to test + * @return true if the string is a valid event id. + */ + fun isEventId(str: String?): Boolean { + return str != null && PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() + } + + /** + * Tells if a string is a valid group id. + * + * @param str the string to test + * @return true if the string is a valid group id. + */ + fun isGroupId(str: String?): Boolean { + return str != null && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches() + } +}// Cannot be instantiated diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixURLSpan.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixURLSpan.kt new file mode 100644 index 00000000..cceba501 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixURLSpan.kt @@ -0,0 +1,18 @@ +package im.vector.matrix.android.api.permalinks + +import android.text.style.ClickableSpan +import android.view.View + +class MatrixURLSpan(private val url: String, + private val callback: Callback? = null) : ClickableSpan() { + + interface Callback { + fun onUrlClicked(url: String) + } + + override fun onClick(widget: View) { + callback?.onUrlClicked(url) + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt new file mode 100644 index 00000000..991526e0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt @@ -0,0 +1,35 @@ +package im.vector.matrix.android.api.permalinks + +import android.text.Spannable +import android.text.SpannableStringBuilder + +object MatrixUrlLinkify { + + /** + * Find the matrix spans i.e matrix id , user id ... to display them as URL. + * + * @param spannableStringBuilder the text in which the matrix items has to be clickable. + */ + fun addLinks(spannableStringBuilder: SpannableStringBuilder, callback: MatrixURLSpan.Callback?) { + // sanity checks + if (spannableStringBuilder.isEmpty()) { + return + } + val text = spannableStringBuilder.toString() + for (index in MatrixPatterns.MATRIX_PATTERNS.indices) { + val pattern = MatrixPatterns.MATRIX_PATTERNS[index] + val matcher = pattern.matcher(spannableStringBuilder) + while (matcher.find()) { + val startPos = matcher.start(0) + if (startPos == 0 || text[startPos - 1] != '/') { + val endPos = matcher.end(0) + val url = text.substring(matcher.start(0), matcher.end(0)) + val span = MatrixURLSpan(url, callback) + spannableStringBuilder.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkUtils.kt new file mode 100644 index 00000000..2bde654a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkUtils.kt @@ -0,0 +1,78 @@ +package im.vector.matrix.android.api.permalinks + +import android.text.TextUtils +import im.vector.matrix.android.api.session.events.model.Event + +/** + * Useful methods to deals with Matrix permalink + */ +object PermalinkUtils { + + private val MATRIX_TO_URL_BASE = "https://matrix.to/#/" + + /** + * Creates a permalink for an event. + * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org" + * + * @param event the event + * @return the permalink, or null in case of error + */ + fun createPermalink(event: Event): String? { + if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) { + return null + } + return createPermalink(event.roomId, event.eventId) + } + + /** + * Creates a permalink for an id (can be a user Id, Room Id, etc.). + * Ex: "https://matrix.to/#/@benoit:matrix.org" + * + * @param id the id + * @return the permalink, or null in case of error + */ + fun createPermalink(id: String): String? { + return if (TextUtils.isEmpty(id)) { + null + } else MATRIX_TO_URL_BASE + escape(id) + + } + + /** + * Creates a permalink for an event. If you have an event you can use [.createPermalink] + * Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org" + * + * @param roomId the id of the room + * @param eventId the id of the event + * @return the permalink + */ + fun createPermalink(roomId: String, eventId: String): String { + return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + } + + /** + * Extract the linked id from the universal link + * + * @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org" + * @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink + */ + fun getLinkedId(url: String?): String? { + val isSupported = url != null && url.startsWith(MATRIX_TO_URL_BASE) + + return if (isSupported) { + url!!.substring(MATRIX_TO_URL_BASE.length) + } else null + + } + + + /** + * Escape '/' in id, because it is used as a separator + * + * @param id the id to escape + * @return the escaped id + */ + private fun escape(id: String): String { + return id.replace("/".toRegex(), "%2F") + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 694e199f..d31274ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -25,7 +25,7 @@ data class Event( @Json(name = "origin_server_ts") val originServerTs: Long? = null, @Json(name = "sender") val sender: String? = null, @Json(name = "state_key") val stateKey: String? = null, - @Json(name = "room_id") var roomId: String? = null, + @Json(name = "room_id") val roomId: String? = null, @Json(name = "unsigned") val unsignedData: UnsignedData? = null, @Json(name = "redacts") val redacts: String? = null