Merge pull request #2708 from element-hq/feature/bma/sdkPermalink

Use sdk API to build permalinks
This commit is contained in:
Benoit Marty 2024-04-15 17:44:43 +02:00 committed by GitHub
commit 3346610f25
10 changed files with 54 additions and 193 deletions

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

@ -0,0 +1 @@
Use sdk API to build permalinks

View file

@ -236,7 +236,7 @@ class MessagesPresenterTest {
val clipboardHelper = FakeClipboardHelper() val clipboardHelper = FakeClipboardHelper()
val event = aMessageEvent() val event = aMessageEvent()
val matrixRoom = FakeMatrixRoom( val matrixRoom = FakeMatrixRoom(
permalinkResult = { Result.success("a link") }, eventPermalinkResult = { Result.success("a link") },
) )
val presenter = createMessagesPresenter( val presenter = createMessagesPresenter(
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,

View file

@ -20,6 +20,7 @@ import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
@ -35,6 +36,8 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import io.element.android.libraries.androidutils.R as AndroidUtilsR import io.element.android.libraries.androidutils.R as AndroidUtilsR
@ -83,34 +86,35 @@ class RoomDetailsNode @AssistedInject constructor(
callbacks.forEach { it.openPollHistory() } callbacks.forEach { it.openPollHistory() }
} }
private fun onShareRoom(context: Context) { private fun CoroutineScope.onShareRoom(context: Context) = launch {
val alias = room.alias ?: room.alternativeAliases.firstOrNull() room.getPermalink()
val permalinkResult = alias?.let { permalinkBuilder.permalinkForRoomAlias(it) } .onSuccess { permalink ->
?: permalinkBuilder.permalinkForRoomId(room.roomId) context.startSharePlainTextIntent(
permalinkResult.onSuccess { permalink -> activityResultLauncher = null,
context.startSharePlainTextIntent( chooserTitle = context.getString(R.string.screen_room_details_share_room_title),
activityResultLauncher = null, text = permalink,
chooserTitle = context.getString(R.string.screen_room_details_share_room_title), noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found)
text = permalink, )
noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found) }
) .onFailure {
}.onFailure { Timber.e(it)
Timber.e(it) }
}
} }
private fun onShareMember(context: Context, member: RoomMember) { private fun onShareMember(context: Context, member: RoomMember) {
val permalinkResult = permalinkBuilder.permalinkForUser(member.userId) val permalinkResult = permalinkBuilder.permalinkForUser(member.userId)
permalinkResult.onSuccess { permalink -> permalinkResult
context.startSharePlainTextIntent( .onSuccess { permalink ->
activityResultLauncher = null, context.startSharePlainTextIntent(
chooserTitle = context.getString(R.string.screen_room_details_share_room_title), activityResultLauncher = null,
text = permalink, chooserTitle = context.getString(R.string.screen_room_details_share_room_title),
noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found) text = permalink,
) noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found)
}.onFailure { )
Timber.e(it) }
} .onFailure {
Timber.e(it)
}
} }
private fun onEditRoomDetails() { private fun onEditRoomDetails() {
@ -131,7 +135,7 @@ class RoomDetailsNode @AssistedInject constructor(
val state = presenter.present() val state = presenter.present()
fun onShareRoom() { fun onShareRoom() {
this.onShareRoom(context) lifecycleScope.onShareRoom(context)
} }
fun onShareMember(roomMember: RoomMember) { fun onShareMember(roomMember: RoomMember) {

View file

@ -16,17 +16,12 @@
package io.element.android.libraries.matrix.api.permalink package io.element.android.libraries.matrix.api.permalink
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.core.UserId
interface PermalinkBuilder { interface PermalinkBuilder {
fun permalinkForUser(userId: UserId): Result<String> fun permalinkForUser(userId: UserId): Result<String>
fun permalinkForRoomAlias(roomAlias: String): Result<String>
fun permalinkForRoomId(roomId: RoomId): Result<String>
} }
sealed class PermalinkBuilderError : Throwable() { sealed class PermalinkBuilderError : Throwable() {
data object InvalidRoomAlias : PermalinkBuilderError()
data object InvalidRoomId : PermalinkBuilderError()
data object InvalidUserId : PermalinkBuilderError() data object InvalidUserId : PermalinkBuilderError()
} }

View file

@ -317,6 +317,11 @@ interface MatrixRoom : Closeable {
*/ */
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
/**
* Get the permalink for the room.
*/
suspend fun getPermalink(): Result<String>
/** /**
* Get the permalink for the provided [eventId]. * Get the permalink for the provided [eventId].
* @param eventId The event id to get the permalink for. * @param eventId The event id to get the permalink for.

View file

@ -17,76 +17,22 @@
package io.element.android.libraries.matrix.impl.permalink package io.element.android.libraries.matrix.impl.permalink
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.MatrixConfiguration
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.MatrixPatterns
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.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilderError import io.element.android.libraries.matrix.api.permalink.PermalinkBuilderError
import org.matrix.rustcomponents.sdk.matrixToUserPermalink
import javax.inject.Inject import javax.inject.Inject
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder { class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder {
private val permalinkBaseUrl
get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL).also {
var baseUrl = it
if (!baseUrl.endsWith("/")) {
baseUrl += "/"
}
if (!baseUrl.endsWith("/#/")) {
baseUrl += "/#/"
}
}
override fun permalinkForUser(userId: UserId): Result<String> { override fun permalinkForUser(userId: UserId): Result<String> {
return if (MatrixPatterns.isUserId(userId.value)) { if (!MatrixPatterns.isUserId(userId.value)) {
val url = buildString { return Result.failure(PermalinkBuilderError.InvalidUserId)
append(permalinkBaseUrl)
if (!isMatrixTo()) {
append(USER_PATH)
}
append(userId.value)
}
Result.success(url)
} else {
Result.failure(PermalinkBuilderError.InvalidUserId)
} }
} return runCatching {
matrixToUserPermalink(userId.value)
override fun permalinkForRoomAlias(roomAlias: String): Result<String> {
return if (MatrixPatterns.isRoomAlias(roomAlias)) {
Result.success(permalinkForRoomAliasOrId(roomAlias))
} else {
Result.failure(PermalinkBuilderError.InvalidRoomAlias)
} }
} }
override fun permalinkForRoomId(roomId: RoomId): Result<String> {
return if (MatrixPatterns.isRoomId(roomId.value)) {
Result.success(permalinkForRoomAliasOrId(roomId.value))
} else {
Result.failure(PermalinkBuilderError.InvalidRoomId)
}
}
private fun permalinkForRoomAliasOrId(value: String): String {
val id = escapeId(value)
return buildString {
append(permalinkBaseUrl)
if (!isMatrixTo()) {
append(ROOM_PATH)
}
append(id)
}
}
private fun escapeId(value: String) = value.replace("/", "%2F")
private fun isMatrixTo(): Boolean = permalinkBaseUrl.startsWith(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL)
companion object {
private const val ROOM_PATH = "room/"
private const val USER_PATH = "user/"
}
} }

View file

@ -16,7 +16,6 @@
package io.element.android.libraries.matrix.impl.room package io.element.android.libraries.matrix.impl.room
import io.element.android.appconfig.MatrixConfiguration
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
@ -731,17 +730,12 @@ class RustMatrixRoom(
) )
} }
override suspend fun getPermalinkFor(eventId: EventId): Result<String> { override suspend fun getPermalink(): Result<String> = runCatching {
// FIXME Use the SDK API once https://github.com/matrix-org/matrix-rust-sdk/issues/3259 has been done innerRoom.matrixToPermalink()
// Now use a simple builder }
return runCatching {
buildString { override suspend fun getPermalinkFor(eventId: EventId): Result<String> = runCatching {
append(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL) innerRoom.matrixToEventPermalink(eventId.value)
append(roomId.value)
append("/")
append(eventId.value)
}
}
} }
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> { private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {

View file

@ -1,80 +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.androidutils.metadata.withReleaseBehavior
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.tests.testutils.assertThrowsInDebug
import org.junit.Test
class DefaultPermalinkBuilderTest {
fun `building a permalink for an invalid user id throws when verifying the id`() {
assertThrowsInDebug {
val userId = UserId("some invalid user id")
DefaultPermalinkBuilder().permalinkForUser(userId)
}
}
fun `building a permalink for an invalid room id throws when verifying the id`() {
assertThrowsInDebug {
val roomId = RoomId("some invalid room id")
DefaultPermalinkBuilder().permalinkForRoomId(roomId)
}
}
@Test
fun `building a permalink for an invalid user id returns failure when not verifying the id`() {
withReleaseBehavior {
val userId = UserId("some invalid user id")
assertThat(DefaultPermalinkBuilder().permalinkForUser(userId).isFailure).isTrue()
}
}
@Test
fun `building a permalink for an invalid room id returns failure when not verifying the id`() {
withReleaseBehavior {
val roomId = RoomId("some invalid room id")
assertThat(DefaultPermalinkBuilder().permalinkForRoomId(roomId).isFailure).isTrue()
}
}
@Test
fun `building a permalink for an invalid room alias returns failure`() {
val roomAlias = "an invalid room alias"
assertThat(DefaultPermalinkBuilder().permalinkForRoomAlias(roomAlias).isFailure).isTrue()
}
@Test
fun `building a permalink for a valid user id returns a matrix-to url`() {
val userId = UserId("@user:matrix.org")
assertThat(DefaultPermalinkBuilder().permalinkForUser(userId).getOrNull()).isEqualTo("https://matrix.to/#/@user:matrix.org")
}
@Test
fun `building a permalink for a valid room id returns a matrix-to url`() {
val roomId = RoomId("!aBCdEFG1234:matrix.org")
assertThat(DefaultPermalinkBuilder().permalinkForRoomId(roomId).getOrNull()).isEqualTo("https://matrix.to/#/!aBCdEFG1234:matrix.org")
}
@Test
fun `building a permalink for a valid room alias returns a matrix-to url`() {
val roomAlias = "#room:matrix.org"
assertThat(DefaultPermalinkBuilder().permalinkForRoomAlias(roomAlias).getOrNull()).isEqualTo("https://matrix.to/#/#room:matrix.org")
}
}

View file

@ -16,7 +16,6 @@
package io.element.android.libraries.matrix.test.permalink package io.element.android.libraries.matrix.test.permalink
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.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
@ -26,12 +25,4 @@ class FakePermalinkBuilder(
override fun permalinkForUser(userId: UserId): Result<String> { override fun permalinkForUser(userId: UserId): Result<String> {
return result() return result()
} }
override fun permalinkForRoomAlias(roomAlias: String): Result<String> {
return result()
}
override fun permalinkForRoomId(roomId: RoomId): Result<String> {
return result()
}
} }

View file

@ -84,7 +84,8 @@ class FakeMatrixRoom(
override val activeMemberCount: Long = 234L, override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
private var permalinkResult: () -> Result<String> = { Result.success("link") }, private var roomPermalinkResult: () -> Result<String> = { Result.success("room link") },
private var eventPermalinkResult: (EventId) -> Result<String> = { Result.success("event link") },
canRedactOwn: Boolean = false, canRedactOwn: Boolean = false,
canRedactOther: Boolean = false, canRedactOther: Boolean = false,
) : MatrixRoom { ) : MatrixRoom {
@ -278,8 +279,12 @@ class FakeMatrixRoom(
return cancelSendResult return cancelSendResult
} }
override suspend fun getPermalink(): Result<String> {
return roomPermalinkResult()
}
override suspend fun getPermalinkFor(eventId: EventId): Result<String> { override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
return permalinkResult() return eventPermalinkResult(eventId)
} }
override suspend fun editMessage( override suspend fun editMessage(