Merge pull request #2709 from element-hq/feature/bma/permalinkParser

Parse permalink using `parseMatrixEntityFrom` from the SDK
This commit is contained in:
Benoit Marty 2024-04-15 20:58:50 +02:00 committed by GitHub
commit ff6c8df61c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 94 additions and 423 deletions

1
changelog.d/2709.misc Normal file
View file

@ -0,0 +1 @@
Parse permalink using parseMatrixEntityFrom from the SDK

View file

@ -17,7 +17,6 @@
package io.element.android.features.messages.impl package io.element.android.features.messages.impl
import android.content.Context import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -108,13 +107,19 @@ class MessagesNode @AssistedInject constructor(
context: Context, context: Context,
url: String, url: String,
) { ) {
when (val permalink = permalinkParser.parse(Uri.parse(url))) { when (val permalink = permalinkParser.parse(url)) {
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
callback?.onUserDataClicked(UserId(permalink.userId)) callback?.onUserDataClicked(permalink.userId)
} }
is PermalinkData.RoomLink -> { is PermalinkData.RoomLink -> {
// TODO Implement room link handling // TODO Implement room link handling
} }
is PermalinkData.EventIdAliasLink -> {
// TODO Implement room and Event link handling
}
is PermalinkData.EventIdLink -> {
// TODO Implement room and Event link handling
}
is PermalinkData.FallbackLink, is PermalinkData.FallbackLink,
is PermalinkData.RoomEmailInviteLink -> { is PermalinkData.RoomEmailInviteLink -> {
context.openUrlInExternalApp(url) context.openUrlInExternalApp(url)

View file

@ -16,7 +16,6 @@
package io.element.android.features.messages.impl.messagecomposer package io.element.android.features.messages.impl.messagecomposer
import android.net.Uri
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.features.messages.impl.mentions.MentionSuggestion
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
@ -49,7 +48,6 @@ fun aMessageComposerState(
richTextEditorState = richTextEditorState, richTextEditorState = richTextEditorState,
permalinkParser = object : PermalinkParser { permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData = TODO() override fun parse(uriString: String): PermalinkData = TODO()
override fun parse(uri: Uri): PermalinkData = TODO()
}, },
isFullScreen = isFullScreen, isFullScreen = isFullScreen,
mode = mode, mode = mode,

View file

@ -18,7 +18,9 @@ package io.element.android.libraries.matrix.api.permalink
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
/** /**
@ -27,28 +29,44 @@ import kotlinx.collections.immutable.ImmutableList
*/ */
@Immutable @Immutable
sealed interface PermalinkData { sealed interface PermalinkData {
data class RoomLink( sealed interface RoomLink : PermalinkData {
val roomIdOrAlias: String,
val isRoomAlias: Boolean,
val eventId: String?,
val viaParameters: ImmutableList<String> val viaParameters: ImmutableList<String>
) : PermalinkData {
fun getRoomId(): RoomId? {
return roomIdOrAlias.takeIf { !isRoomAlias }?.let(::RoomId)
}
fun getRoomAlias(): String? {
return roomIdOrAlias.takeIf { isRoomAlias }
}
} }
data class RoomIdLink(
val roomId: RoomId,
override val viaParameters: ImmutableList<String>
) : RoomLink
data class RoomAliasLink(
val roomAlias: String,
override val viaParameters: ImmutableList<String>
) : RoomLink
sealed interface EventLink : PermalinkData {
val eventId: EventId
val viaParameters: ImmutableList<String>
}
data class EventIdLink(
val roomId: RoomId,
override val eventId: EventId,
override val viaParameters: ImmutableList<String>
) : EventLink
data class EventIdAliasLink(
val roomAlias: String,
override val eventId: EventId,
override val viaParameters: ImmutableList<String>
) : EventLink
/* /*
* &room_name=Team2 * &room_name=Team2
* &room_avatar_url=mxc: * &room_avatar_url=mxc:
* &inviter_name=bob * &inviter_name=bob
*/ */
data class RoomEmailInviteLink( data class RoomEmailInviteLink(
val roomId: String, val roomId: RoomId,
val email: String, val email: String,
val signUrl: String, val signUrl: String,
val roomName: String?, val roomName: String?,
@ -60,7 +78,7 @@ sealed interface PermalinkData {
val roomType: String? val roomType: String?
) : PermalinkData ) : PermalinkData
data class UserLink(val userId: String) : PermalinkData data class UserLink(val userId: UserId) : PermalinkData
data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData
} }

View file

@ -16,8 +16,6 @@
package io.element.android.libraries.matrix.api.permalink package io.element.android.libraries.matrix.api.permalink
import android.net.Uri
/** /**
* This class turns a uri to a [PermalinkData]. * This class turns a uri to a [PermalinkData].
* element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
@ -27,12 +25,7 @@ import android.net.Uri
interface PermalinkParser { interface PermalinkParser {
/** /**
* Turns a uri string to a [PermalinkData]. * Turns a uri string to a [PermalinkData].
*/
fun parse(uriString: String): PermalinkData
/**
* Turns a uri to a [PermalinkData].
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
*/ */
fun parse(uri: Uri): PermalinkData fun parse(uriString: String): PermalinkData
} }

View file

@ -22,5 +22,6 @@ import io.element.android.libraries.matrix.api.core.UserId
sealed interface Mention { sealed interface Mention {
data class User(val userId: UserId) : Mention data class User(val userId: UserId) : Mention
data object AtRoom : Mention data object AtRoom : Mention
data class Room(val roomId: RoomId?, val roomAlias: String?) : Mention data class Room(val roomId: RoomId) : Mention
data class RoomAlias(val roomAlias: String?) : Mention
} }

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.permalink
import com.google.common.truth.Truth.assertThat
import kotlinx.collections.immutable.persistentListOf
import org.junit.Test
class PermalinkDataTest {
@Test
fun `getRoomId() returns value when isRoomAlias is false`() {
val permalinkData = PermalinkData.RoomLink(
roomIdOrAlias = "!abcdef123456:matrix.org",
isRoomAlias = false,
eventId = null,
viaParameters = persistentListOf(),
)
assertThat(permalinkData.getRoomId()).isNotNull()
assertThat(permalinkData.getRoomAlias()).isNull()
}
@Test
fun `getRoomAlias() returns value when isRoomAlias is true`() {
val permalinkData = PermalinkData.RoomLink(
roomIdOrAlias = "#room:matrix.org",
isRoomAlias = true,
eventId = null,
viaParameters = persistentListOf(),
)
assertThat(permalinkData.getRoomId()).isNull()
assertThat(permalinkData.getRoomAlias()).isNotNull()
}
}

View file

@ -17,16 +17,17 @@
package io.element.android.libraries.matrix.impl.permalink package io.element.android.libraries.matrix.impl.permalink
import android.net.Uri import android.net.Uri
import android.net.UrlQuerySanitizer
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.MatrixToConverter import io.element.android.libraries.matrix.api.permalink.MatrixToConverter
import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber import org.matrix.rustcomponents.sdk.MatrixId
import java.net.URLDecoder import org.matrix.rustcomponents.sdk.parseMatrixEntityFrom
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -41,118 +42,45 @@ class DefaultPermalinkParser @Inject constructor(
) : PermalinkParser { ) : PermalinkParser {
/** /**
* Turns a uri string to a [PermalinkData]. * Turns a uri string to a [PermalinkData].
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
*/ */
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
val uri = Uri.parse(uriString) val uri = Uri.parse(uriString)
return parse(uri)
}
/**
* Turns a uri to a [PermalinkData].
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
*/
override fun parse(uri: Uri): PermalinkData {
// the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
// mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
// so convert URI to matrix.to to simplify parsing process // so convert URI to matrix.to to simplify parsing process
val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri) val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
// We can't use uri.fragment as it is decoding to early and it will break the parsing val result = runCatching {
// of parameters that represents url (like signurl) parseMatrixEntityFrom(matrixToUri.toString())
val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment }.getOrNull()
if (fragment.isEmpty()) { return if (result == null) {
return PermalinkData.FallbackLink(uri) PermalinkData.FallbackLink(uri)
}
val safeFragment = fragment.substringBefore('?')
val viaQueryParameters = fragment.getViaParameters()
// we are limiting to 2 params
val params = safeFragment
.split(MatrixPatterns.SEP_REGEX)
.filter { it.isNotEmpty() }
.take(2)
val decodedParams = params
.map { URLDecoder.decode(it, "UTF-8") }
val identifier = params.getOrNull(0)
val decodedIdentifier = decodedParams.getOrNull(0)
val extraParameter = decodedParams.getOrNull(1)
return when {
identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier)
MatrixPatterns.isRoomId(decodedIdentifier) -> {
handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters)
}
MatrixPatterns.isRoomAlias(decodedIdentifier) -> {
PermalinkData.RoomLink(
roomIdOrAlias = decodedIdentifier,
isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters.toImmutableList()
)
}
else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier))
}
}
private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData {
// Can't rely on built in parsing because it's messing around the signurl
val paramList = safeExtractParams(fragment)
val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second
val email = paramList.firstOrNull { it.first == "email" }?.second
return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) {
try {
val signValidUri = Uri.parse(signUrl)
val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException("missing `authority`")
val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException("missing `token`")
val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException("missing `private_key`")
PermalinkData.RoomEmailInviteLink(
roomId = identifier,
email = email!!,
signUrl = signUrl!!,
roomName = paramList.firstOrNull { it.first == "room_name" }?.second,
inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second,
roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second,
roomType = paramList.firstOrNull { it.first == "room_type" }?.second,
identityServer = identityServerHost,
token = token,
privateKey = privateKey
)
} catch (failure: Throwable) {
Timber.i("## Permalink: Failed to parse permalink $signUrl")
PermalinkData.FallbackLink(uri)
}
} else { } else {
PermalinkData.RoomLink( val viaParameters = result.via.toImmutableList()
roomIdOrAlias = identifier, when (val id = result.id) {
isRoomAlias = false, is MatrixId.Room -> PermalinkData.RoomIdLink(
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, roomId = RoomId(id.id),
viaParameters = viaQueryParameters.toImmutableList() viaParameters = viaParameters,
) )
} is MatrixId.User -> PermalinkData.UserLink(
} userId = UserId(id.id),
)
private fun safeExtractParams(fragment: String) = is MatrixId.RoomAlias -> PermalinkData.RoomAliasLink(
fragment.substringAfter("?").split('&').mapNotNull { roomAlias = id.alias,
val splitNameValue = it.split("=") viaParameters = viaParameters,
if (splitNameValue.size == 2) { )
Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) is MatrixId.EventOnRoomId -> PermalinkData.EventIdLink(
} else { roomId = RoomId(id.roomId),
null eventId = EventId(id.eventId),
viaParameters = viaParameters,
)
is MatrixId.EventOnRoomAlias -> PermalinkData.EventIdAliasLink(
roomAlias = id.alias,
eventId = EventId(id.eventId),
viaParameters = viaParameters,
)
} }
} }
private fun String.getViaParameters(): List<String> {
return runCatching {
UrlQuerySanitizer(this)
.parameterList
.filter {
it.mParameter == "via"
}
.map {
URLDecoder.decode(it.mValue, "UTF-8")
}
}.getOrDefault(emptyList())
} }
} }

View file

@ -1,204 +0,0 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.matrix.impl.permalink
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import kotlinx.collections.immutable.persistentListOf
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class DefaultPermalinkParserTest {
@Test
fun `parsing an invalid url returns a fallback link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://element.io"
assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java)
}
@Test
fun `parsing an invalid url with the right path but no content returns a fallback link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/user"
assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java)
}
@Test
fun `parsing an invalid url with the right path but empty content returns a fallback link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/user/"
assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java)
}
@Test
fun `parsing an invalid url with the right path but invalid content returns a fallback link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/user/some%20user!"
assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java)
}
@Test
fun `parsing a valid user url returns a user link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/user/@test:matrix.org"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.UserLink(
userId = "@test:matrix.org"
)
)
}
@Test
fun `parsing a valid room id url returns a room link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomLink(
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = null,
viaParameters = persistentListOf(),
)
)
}
@Test
fun `parsing a valid room id with event id url returns a room link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomLink(
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = "\$1234567890abcdef:matrix.org",
viaParameters = persistentListOf(),
)
)
}
@Test
fun `parsing a valid room id with and invalid event id url returns a room link with no event id`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/1234567890abcdef:matrix.org"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomLink(
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = null,
viaParameters = persistentListOf(),
)
)
}
@Test
fun `parsing a valid room id with event id and via parameters url returns a room link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org?via=matrix.org&via=matrix.com"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomLink(
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = "\$1234567890abcdef:matrix.org",
viaParameters = persistentListOf("matrix.org", "matrix.com"),
)
)
}
@Test
fun `parsing a valid room alias url returns a room link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/#element-android:matrix.org"
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomLink(
roomIdOrAlias = "#element-android:matrix.org",
isRoomAlias = true,
eventId = null,
viaParameters = persistentListOf(),
)
)
}
@Test
fun `parsing a url with an invalid signurl returns a fallback link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
// This url has no private key
val url = "https://app.element.io/#/room/%21aBCDEF12345%3Amatrix.org" +
"?email=testuser%40element.io" +
"&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3Da_token" +
"&room_name=TestRoom" +
"&room_avatar_url=" +
"&inviter_name=User" +
"&guest_access_token=" +
"&guest_user_id=" +
"&room_type="
assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java)
}
@Test
fun `parsing a url with signurl returns a room email invite link`() {
val sut = DefaultPermalinkParser(
matrixToConverter = DefaultMatrixToConverter(),
)
val url = "https://app.element.io/#/room/%21aBCDEF12345%3Amatrix.org" +
"?email=testuser%40element.io" +
"&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3Da_token%26private_key%3Da_private_key" +
"&room_name=TestRoom" +
"&room_avatar_url=" +
"&inviter_name=User" +
"&guest_access_token=" +
"&guest_user_id=" +
"&room_type="
assertThat(sut.parse(url)).isEqualTo(
PermalinkData.RoomEmailInviteLink(
roomId = "!aBCDEF12345:matrix.org",
email = "testuser@element.io",
signUrl = "https://vector.im/_matrix/identity/api/v1/sign-ed25519?token=a_token&private_key=a_private_key",
roomName = "TestRoom",
roomAvatarUrl = "",
inviterName = "User",
identityServer = "vector.im",
token = "a_token",
privateKey = "a_private_key",
roomType = "",
)
)
}
}

View file

@ -16,7 +16,6 @@
package io.element.android.libraries.matrix.test.permalink package io.element.android.libraries.matrix.test.permalink
import android.net.Uri
import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@ -30,8 +29,4 @@ class FakePermalinkParser(
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return result() return result()
} }
override fun parse(uri: Uri): PermalinkData {
TODO("Not yet implemented")
}
} }

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrixui.messages
import android.net.Uri import android.net.Uri
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
@ -78,10 +79,8 @@ class ToHtmlDocumentTest {
val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser { val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return PermalinkData.UserLink("@alice:matrix.org") return PermalinkData.UserLink(UserId("@alice:matrix.org"))
} }
override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented")
}) })
assertThat(document?.text()).isEqualTo("Hey @Alice!") assertThat(document?.text()).isEqualTo("Hey @Alice!")
} }
@ -95,10 +94,8 @@ class ToHtmlDocumentTest {
val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser { val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return PermalinkData.UserLink("@alice:matrix.org") return PermalinkData.UserLink(UserId("@alice:matrix.org"))
} }
override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented")
}) })
assertThat(document?.text()).isEqualTo("Hey @Alice!") assertThat(document?.text()).isEqualTo("Hey @Alice!")
} }
@ -114,8 +111,6 @@ class ToHtmlDocumentTest {
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return PermalinkData.FallbackLink(uri = Uri.parse("https://matrix.org")) return PermalinkData.FallbackLink(uri = Uri.parse("https://matrix.org"))
} }
override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented")
}) })
assertThat(document?.text()).isEqualTo("Hey Alice!") assertThat(document?.text()).isEqualTo("Hey Alice!")
} }

View file

@ -914,7 +914,6 @@ private fun ATextComposer(
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
permalinkParser = object : PermalinkParser { permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData = TODO("Not yet implemented") override fun parse(uriString: String): PermalinkData = TODO("Not yet implemented")
override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented")
}, },
composerMode = composerMode, composerMode = composerMode,
enableTextFormatting = enableTextFormatting, enableTextFormatting = enableTextFormatting,

View file

@ -18,7 +18,6 @@ package io.element.android.libraries.textcomposer.mentions
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -41,6 +40,7 @@ import io.element.android.libraries.designsystem.theme.currentUserMentionPillTex
import io.element.android.libraries.designsystem.theme.mentionPillBackground import io.element.android.libraries.designsystem.theme.mentionPillBackground
import io.element.android.libraries.designsystem.theme.mentionPillText import io.element.android.libraries.designsystem.theme.mentionPillText
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -80,7 +80,7 @@ class MentionSpanProvider(
val (startPaddingPx, endPaddingPx) = paddingValuesPx.value val (startPaddingPx, endPaddingPx) = paddingValuesPx.value
return when { return when {
permalinkData is PermalinkData.UserLink -> { permalinkData is PermalinkData.UserLink -> {
val isCurrentUser = permalinkData.userId == currentSessionId.value val isCurrentUser = permalinkData.userId == currentSessionId
MentionSpan( MentionSpan(
type = MentionSpan.Type.USER, type = MentionSpan.Type.USER,
backgroundColor = if (isCurrentUser) currentUserBackgroundColor else otherBackgroundColor, backgroundColor = if (isCurrentUser) currentUserBackgroundColor else otherBackgroundColor,
@ -137,19 +137,15 @@ internal fun MentionSpanPreview() {
permalinkParser = object : PermalinkParser { permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return when (uriString) { return when (uriString) {
"https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink("@me:matrix.org") "https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org"))
"https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink("@other:matrix.org") "https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org"))
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink( "https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomAliasLink(
roomIdOrAlias = "#room:matrix.org", roomAlias = "#room:matrix.org",
isRoomAlias = true,
eventId = null,
viaParameters = persistentListOf(), viaParameters = persistentListOf(),
) )
else -> TODO() else -> TODO()
} }
} }
override fun parse(uri: Uri): PermalinkData = TODO()
}, },
) )
ElementPreview { ElementPreview {

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.textcomposer.impl.mentions
import android.graphics.Color import android.graphics.Color
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
@ -50,7 +51,7 @@ class MentionSpanProviderTest {
@Test @Test
fun `getting mention span for current user should return a MentionSpan with custom colors`() { fun `getting mention span for current user should return a MentionSpan with custom colors`() {
permalinkParser.givenResult(PermalinkData.UserLink(currentUserId.value)) permalinkParser.givenResult(PermalinkData.UserLink(currentUserId))
val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${currentUserId.value}") val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${currentUserId.value}")
assertThat(mentionSpan.backgroundColor).isEqualTo(myUserColor) assertThat(mentionSpan.backgroundColor).isEqualTo(myUserColor)
assertThat(mentionSpan.textColor).isEqualTo(myUserColor) assertThat(mentionSpan.textColor).isEqualTo(myUserColor)
@ -58,7 +59,7 @@ class MentionSpanProviderTest {
@Test @Test
fun `getting mention span for other user should return a MentionSpan with normal colors`() { fun `getting mention span for other user should return a MentionSpan with normal colors`() {
permalinkParser.givenResult(PermalinkData.UserLink("@other:matrix.org")) permalinkParser.givenResult(PermalinkData.UserLink(UserId("@other:matrix.org")))
val mentionSpan = mentionSpanProvider.getMentionSpanFor("@other:matrix.org", "https://matrix.to/#/@other:matrix.org") val mentionSpan = mentionSpanProvider.getMentionSpanFor("@other:matrix.org", "https://matrix.to/#/@other:matrix.org")
assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor)
assertThat(mentionSpan.textColor).isEqualTo(otherColor) assertThat(mentionSpan.textColor).isEqualTo(otherColor)
@ -67,10 +68,8 @@ class MentionSpanProviderTest {
@Test @Test
fun `getting mention span for a room should return a MentionSpan with normal colors`() { fun `getting mention span for a room should return a MentionSpan with normal colors`() {
permalinkParser.givenResult( permalinkParser.givenResult(
PermalinkData.RoomLink( PermalinkData.RoomAliasLink(
roomIdOrAlias = "#room:matrix.org", roomAlias = "#room:matrix.org",
isRoomAlias = true,
eventId = null,
viaParameters = persistentListOf(), viaParameters = persistentListOf(),
) )
) )
@ -82,10 +81,8 @@ class MentionSpanProviderTest {
@Test @Test
fun `getting mention span for @room should return a MentionSpan with normal colors`() { fun `getting mention span for @room should return a MentionSpan with normal colors`() {
permalinkParser.givenResult( permalinkParser.givenResult(
PermalinkData.RoomLink( PermalinkData.RoomAliasLink(
roomIdOrAlias = "#", roomAlias = "#",
isRoomAlias = true,
eventId = null,
viaParameters = persistentListOf(), viaParameters = persistentListOf(),
) )
) )

View file

@ -24,8 +24,4 @@ class OnlyFallbackPermalinkParser : PermalinkParser {
override fun parse(uriString: String): PermalinkData { override fun parse(uriString: String): PermalinkData {
return PermalinkData.FallbackLink(Uri.parse(uriString)) return PermalinkData.FallbackLink(Uri.parse(uriString))
} }
override fun parse(uri: Uri): PermalinkData {
return PermalinkData.FallbackLink(uri)
}
} }