diff --git a/changelog.d/254.feature b/changelog.d/254.feature new file mode 100644 index 0000000000..36cc877a75 --- /dev/null +++ b/changelog.d/254.feature @@ -0,0 +1 @@ +[Create and join rooms] Improve user search results calling the "profile" API diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSource.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSource.kt index e021d17144..b79ccc1d5b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSource.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSource.kt @@ -31,8 +31,7 @@ class AllMatrixUsersDataSource @Inject constructor( } override suspend fun getProfile(userId: UserId): MatrixUser? { - // TODO hook up to matrix client - return null + return client.getProfile(userId).getOrNull() } companion object { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index a94b3bf28b..a80cf821fd 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -16,7 +16,6 @@ package io.element.android.features.createroom.impl.root -import android.widget.Toast import androidx.annotation.DrawableRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -88,10 +87,7 @@ fun CreateRoomRootView( modifier = Modifier.fillMaxWidth(), state = state.userListState, onUserSelected = { - // Fixme disabled DM creation since it can break the account data which is not correctly synced - // uncomment to enable it again or move behind a feature flag - Toast.makeText(context, "Create DM feature is disabled.", Toast.LENGTH_SHORT).show() -// state.eventSink(CreateRoomRootEvents.StartDM(it)) + state.eventSink(CreateRoomRootEvents.StartDM(it)) }, ) @@ -108,6 +104,7 @@ fun CreateRoomRootView( is Async.Loading -> { ProgressDialog(text = stringResource(id = StringR.string.common_creating_room)) } + is Async.Failure -> { RetryDialog( content = stringResource(id = R.string.screen_start_chat_error_starting_chat), @@ -120,6 +117,7 @@ fun CreateRoomRootView( }, ) } + else -> Unit } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt index 1dbef27f97..f20e46d1ba 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt @@ -47,18 +47,7 @@ internal class AllMatrixUsersDataSourceTest { val dataSource = AllMatrixUsersDataSource(matrixClient) val results = dataSource.search("test") - Truth.assertThat(results).containsExactly( - MatrixUser( - userId = A_USER_ID, - displayName = A_USER_NAME, - avatarUrl = AN_AVATAR_URL - ), - MatrixUser( - userId = A_USER_ID_2, - displayName = A_USER_NAME, - avatarUrl = AN_AVATAR_URL - ), - ) + Truth.assertThat(results).containsExactly(aMatrixUserProfile(), aMatrixUserProfile(userId = A_USER_ID_2)) } @Test @@ -74,6 +63,32 @@ internal class AllMatrixUsersDataSourceTest { Truth.assertThat(results).isEmpty() } + @Test + fun `get profile - returns user on success`() = runTest { + val matrixClient = FakeMatrixClient() + matrixClient.givenGetProfileResult( + userId = A_USER_ID, + result = Result.success(aMatrixUserProfile()) + ) + val dataSource = AllMatrixUsersDataSource(matrixClient) + + val result = dataSource.getProfile(A_USER_ID) + Truth.assertThat(result).isEqualTo(aMatrixUserProfile()) + } + + @Test + fun `get profile - returns null on error`() = runTest { + val matrixClient = FakeMatrixClient() + matrixClient.givenGetProfileResult( + userId = A_USER_ID, + result = Result.failure(Throwable("Ruhroh")) + ) + val dataSource = AllMatrixUsersDataSource(matrixClient) + + val result = dataSource.getProfile(A_USER_ID) + Truth.assertThat(result).isNull() + } + private fun aMatrixUserProfile( userId: UserId = A_USER_ID, displayName: String = A_USER_NAME, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 92da3ad557..dce2b15485 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import java.io.Closeable @@ -40,6 +41,7 @@ interface MatrixClient : Closeable { suspend fun unignoreUser(userId: UserId): Result suspend fun createRoom(createRoomParams: CreateRoomParameters): Result suspend fun createDM(userId: UserId): Result + suspend fun getProfile(userId: UserId): Result fun startSync() fun stopSync() fun mediaResolver(): MediaResolver @@ -60,5 +62,5 @@ interface MatrixClient : Closeable { fun roomMembershipObserver(): RoomMembershipObserver - suspend fun searchUsers(searchTerm: String, limit: Long): Result + suspend fun searchUsers(searchTerm: String, limit: Long): Result } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 3e90b55d93..73e37121d5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.impl.media.RustMediaResolver import io.element.android.libraries.matrix.impl.notification.RustNotificationService @@ -37,10 +38,12 @@ import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy +import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -268,6 +271,12 @@ class RustMatrixClient constructor( return createRoom(createRoomParams) } + override suspend fun getProfile(userId: UserId): Result = withContext(Dispatchers.IO) { + runCatching { + client.getProfile(userId.value).let(UserProfileMapper::map) + } + } + override fun mediaResolver(): MediaResolver = mediaResolver override fun sessionVerificationService(): SessionVerificationService = verificationService @@ -367,7 +376,7 @@ class RustMatrixClient constructor( override suspend fun searchUsers(searchTerm: String, limit: Long): Result = withContext(dispatchers.io) { runCatching { - UserSearchResultMapper.map(client.searchUsers(searchTerm, limit.toULong())) + client.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserProfileMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserProfileMapper.kt new file mode 100644 index 0000000000..5b6db0b224 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserProfileMapper.kt @@ -0,0 +1,30 @@ +/* + * 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.impl.usersearch + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser +import org.matrix.rustcomponents.sdk.UserProfile + +object UserProfileMapper { + fun map(userProfile: UserProfile): MatrixUser = + MatrixUser( + userId = UserId(userProfile.userId), + displayName = userProfile.displayName, + avatarUrl = userProfile.avatarUrl, + ) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt index c4e18e64fc..1ec0b512ec 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt @@ -16,26 +16,15 @@ package io.element.android.libraries.matrix.impl.usersearch -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults -import io.element.android.libraries.matrix.api.user.MatrixUser import org.matrix.rustcomponents.sdk.SearchUsersResults -import org.matrix.rustcomponents.sdk.UserProfile object UserSearchResultMapper { fun map(result: SearchUsersResults): MatrixSearchUserResults { return MatrixSearchUserResults( - results = result.results.map(::mapUserProfile), + results = result.results.map(UserProfileMapper::map), limited = result.limited, ) } - - private fun mapUserProfile(userProfile: UserProfile): MatrixUser { - return MatrixUser( - userId = UserId(userProfile.userId), - displayName = userProfile.displayName, - avatarUrl = userProfile.avatarUrl, - ) - } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 207ddc252c..9d3ce40da1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.test.media.FakeMediaResolver import io.element.android.libraries.matrix.test.notification.FakeNotificationService @@ -57,6 +58,7 @@ class FakeMatrixClient( private var logoutFailure: Throwable? = null private val getRoomResults = mutableMapOf() private val searchUserResults = mutableMapOf>() + private val getProfileResults = mutableMapOf>() override fun getRoom(roomId: RoomId): MatrixRoom? { return getRoomResults[roomId] @@ -85,6 +87,10 @@ class FakeMatrixClient( return createDmResult } + override suspend fun getProfile(userId: UserId): Result { + return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId")) + } + override fun startSync() = Unit override fun stopSync() = Unit @@ -169,4 +175,8 @@ class FakeMatrixClient( fun givenSearchUsersResult(searchTerm: String, result: Result) { searchUserResults[searchTerm] = result } + + fun givenGetProfileResult(userId: UserId, result: Result) { + getProfileResults[userId] = result + } }