Merge pull request #2674 from element-hq/feature/bma/roomSuggestion

Room / User suggestions
This commit is contained in:
Benoit Marty 2024-04-08 17:26:54 +02:00 committed by GitHub
commit 014061facf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 573 additions and 53 deletions

View file

@ -34,6 +34,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.R
import io.element.android.libraries.designsystem.modifiers.blurCompat
import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow
@ -171,6 +172,7 @@ internal fun ElementLogoAtomLargeNoBlurShadowPreview() = ElementPreview {
ContentToPreview(ElementLogoAtomSize.Large, useBlurredShadow = false)
}
@ExcludeFromCoverage
@Composable
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize, useBlurredShadow: Boolean = true) {
Box(

View file

@ -36,6 +36,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -162,6 +163,7 @@ internal fun PreferenceTextWithEndBadgeDarkPreview() = ElementPreviewDark {
ContentToPreview(showEndBadge = true)
}
@ExcludeFromCoverage
@Composable
private fun ContentToPreview(showEndBadge: Boolean) {
Column(

View file

@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@ -269,6 +270,7 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ExcludeFromCoverage
private fun ContentToPreview(
query: String = "",
active: Boolean = false,

View file

@ -91,4 +91,7 @@ interface MatrixClient : Closeable {
fun roomMembershipObserver(): RoomMembershipObserver
fun isMe(userId: UserId?) = userId == sessionId
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
}

View file

@ -82,6 +82,12 @@ interface MatrixRoom : Closeable {
*/
suspend fun updateMembers()
/**
* Get the members of the room. Note: generally this should not be used, please use
* [membersStateFlow] and [updateMembers] instead.
*/
suspend fun getMembers(limit: Int = 5): Result<List<RoomMember>>
/**
* Will return an updated member or an error.
*/

View file

@ -0,0 +1,65 @@
/*
* 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.api.room.recent
import io.element.android.libraries.matrix.api.MatrixClient
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.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.coroutines.flow.first
private const val MAX_RECENT_DIRECT_ROOMS_TO_RETURN = 5
data class RecentDirectRoom(
val roomId: RoomId,
val matrixUser: MatrixUser,
)
suspend fun MatrixClient.getRecentDirectRooms(
maxNumberOfResults: Int = MAX_RECENT_DIRECT_ROOMS_TO_RETURN,
): List<RecentDirectRoom> {
val result = mutableListOf<RecentDirectRoom>()
val foundUserIds = mutableSetOf<UserId>()
getRecentlyVisitedRooms().getOrNull()?.let { roomIds ->
roomIds
.mapNotNull { roomId -> getRoom(roomId) }
.filter { it.isDm && it.isJoined() }
.map { room ->
val otherUser = room.getMembers().getOrNull()
?.firstOrNull { it.userId != sessionId }
?.takeIf { foundUserIds.add(it.userId) }
?.toMatrixUser()
if (otherUser != null) {
result.add(
RecentDirectRoom(room.roomId, otherUser)
)
// Return early to avoid useless computation
if (result.size >= maxNumberOfResults) {
return@map
}
}
}
}
return result
}
suspend fun MatrixRoom.isJoined(): Boolean {
return roomInfoFlow.first().currentUserMembership == CurrentUserMembership.JOINED
}

View file

@ -438,6 +438,18 @@ class RustMatrixClient(
}
}
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
runCatching {
client.trackRecentlyVisitedRoom(roomId.value)
}
}
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> = withContext(sessionDispatcher) {
runCatching {
client.getRecentlyVisitedRooms().map(::RoomId)
}
}
override fun syncService(): SyncService = rustSyncService
override fun sessionVerificationService(): SessionVerificationService = verificationService

View file

@ -237,6 +237,16 @@ class RustMatrixRoom(
roomMemberListFetcher.fetchRoomMembers(source = source)
}
override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) {
runCatching {
innerRoom.members().use {
it.nextChunk(limit.toUInt()).orEmpty().map { roomMember ->
RoomMemberMapper.map(roomMember)
}
}
}
}
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
runCatching {
RoomMemberMapper.map(innerRoom.member(userId.value))

View file

@ -255,4 +255,16 @@ class FakeMatrixClient(
fun givenRemoveAvatarResult(result: Result<Unit>) {
removeAvatarResult = result
}
private val visitedRoomsId: MutableList<RoomId> = mutableListOf()
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> {
visitedRoomsId.removeAll { it == roomId }
visitedRoomsId.add(0, roomId)
return Result.success(Unit)
}
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
return Result.success(visitedRoomsId)
}
}

View file

@ -201,6 +201,10 @@ class FakeMatrixRoom(
return getRoomMemberResult
}
override suspend fun getMembers(limit: Int): Result<List<RoomMember>> {
return Result.success(emptyList())
}
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)