misc : remove direct usage of RoomSummary in UI and let RoomSummary use RoomInfo.

This commit is contained in:
ganfra 2024-10-08 21:34:34 +02:00
parent 5a4e5d0575
commit eeb6b6f4bf
47 changed files with 628 additions and 467 deletions

View file

@ -33,6 +33,13 @@ data class MatrixRoomInfo(
val canonicalAlias: RoomAlias?,
val alternativeAliases: ImmutableList<RoomAlias>,
val currentUserMembership: CurrentUserMembership,
/**
* Member who invited the current user to a room that's in the invited
* state.
*
* Can be missing if the room membership invite event is missing from the
* store.
*/
val inviter: RoomMember?,
val activeMembersCount: Long,
val invitedMembersCount: Long,
@ -43,7 +50,26 @@ data class MatrixRoomInfo(
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val activeRoomCallParticipants: ImmutableList<UserId>,
val isMarkedUnread: Boolean,
/**
* "Interesting" messages received in that room, independently of the
* notification settings.
*/
val numUnreadMessages: Long,
/**
* Events that will notify the user, according to their
* notification settings.
*/
val numUnreadNotifications: Long,
/**
* Events causing mentions/highlights for the user, according to their
* notification settings.
*/
val numUnreadMentions: Long,
val heroes: ImmutableList<MatrixUser>,
val pinnedEventIds: ImmutableList<EventId>,
val creator: UserId?,
)
) {
val aliases: List<RoomAlias>
get() = listOfNotNull(canonicalAlias) + alternativeAliases
}

View file

@ -7,35 +7,14 @@
package io.element.android.libraries.matrix.api.roomlist
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.user.MatrixUser
data class RoomSummary(
val roomId: RoomId,
val name: String?,
val canonicalAlias: RoomAlias?,
val alternativeAliases: List<RoomAlias>,
val isDirect: Boolean,
val avatarUrl: String?,
val info: MatrixRoomInfo,
val lastMessage: RoomMessage?,
val numUnreadMessages: Int,
val numUnreadMentions: Int,
val numUnreadNotifications: Int,
val isMarkedUnread: Boolean,
val inviter: RoomMember?,
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val isDm: Boolean,
val isFavorite: Boolean,
val currentUserMembership: CurrentUserMembership,
val heroes: List<MatrixUser>,
) {
val roomId = info.id
val lastMessageTimestamp = lastMessage?.originServerTs
val aliases: List<RoomAlias>
get() = listOfNotNull(canonicalAlias) + alternativeAliases
val isOneToOne get() = info.activeMembersCount == 2L
}

View file

@ -53,6 +53,10 @@ class MatrixRoomInfoMapper {
activeRoomCallParticipants = it.activeRoomCallParticipants.map(::UserId).toImmutableList(),
heroes = it.elementHeroes().toImmutableList(),
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
isMarkedUnread = it.isMarkedUnread,
numUnreadMessages = it.numUnreadMessages.toLong(),
numUnreadMentions = it.numUnreadMentions.toLong(),
numUnreadNotifications = it.numUnreadNotifications.toLong(),
)
}
}

View file

@ -31,7 +31,7 @@ internal class RoomListFactory(
private val innerRoomListService: RoomListService,
private val sessionCoroutineScope: CoroutineScope,
) {
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory()
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory()
/**
* Creates a room list that can be used to load more rooms and filter them dynamically.

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
@ -17,19 +18,19 @@ val RoomListFilter.predicate
is RoomListFilter.Any -> { _: RoomSummary -> true }
RoomListFilter.None -> { _: RoomSummary -> false }
RoomListFilter.Category.Group -> { roomSummary: RoomSummary ->
!roomSummary.isDm && !roomSummary.isInvited()
!roomSummary.info.isDm && !roomSummary.isInvited()
}
RoomListFilter.Category.People -> { roomSummary: RoomSummary ->
roomSummary.isDm && !roomSummary.isInvited()
roomSummary.info.isDm && !roomSummary.isInvited()
}
RoomListFilter.Favorite -> { roomSummary: RoomSummary ->
roomSummary.isFavorite && !roomSummary.isInvited()
roomSummary.info.isFavorite && !roomSummary.isInvited()
}
RoomListFilter.Unread -> { roomSummary: RoomSummary ->
!roomSummary.isInvited() && (roomSummary.numUnreadNotifications > 0 || roomSummary.isMarkedUnread)
!roomSummary.isInvited() && (roomSummary.info.numUnreadNotifications > 0 || roomSummary.info.isMarkedUnread)
}
is RoomListFilter.NormalizedMatchRoomName -> { roomSummary: RoomSummary ->
roomSummary.name.orEmpty().contains(pattern, ignoreCase = true)
roomSummary.info.name.orEmpty().contains(pattern, ignoreCase = true)
}
RoomListFilter.Invite -> { roomSummary: RoomSummary ->
roomSummary.isInvited()
@ -50,4 +51,4 @@ fun List<RoomSummary>.filter(filter: RoomListFilter): List<RoomSummary> {
}
}
private fun RoomSummary.isInvited() = currentUserMembership == CurrentUserMembership.INVITED
private fun RoomSummary.isInvited() = info.currentUserMembership == CurrentUserMembership.INVITED

View file

@ -1,51 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper
import io.element.android.libraries.matrix.impl.room.elementHeroes
import io.element.android.libraries.matrix.impl.room.map
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.use
class RoomSummaryDetailsFactory(
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
) {
suspend fun create(roomListItem: RoomListItem): RoomSummary {
val roomInfo = roomListItem.roomInfo()
val latestRoomMessage = roomListItem.latestEvent().use { event ->
roomMessageFactory.create(event)
}
return RoomSummary(
roomId = RoomId(roomInfo.id),
name = roomInfo.displayName,
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
alternativeAliases = roomInfo.alternativeAliases.map(::RoomAlias),
isDirect = roomInfo.isDirect,
avatarUrl = roomInfo.avatarUrl,
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
isMarkedUnread = roomInfo.isMarkedUnread,
lastMessage = latestRoomMessage,
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
userDefinedNotificationMode = roomInfo.cachedUserDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
hasRoomCall = roomInfo.hasRoomCall,
isDm = isDm(isDirect = roomInfo.isDirect, activeMembersCount = roomInfo.activeMembersCount.toInt()),
isFavorite = roomInfo.isFavourite,
currentUserMembership = roomInfo.membership.map(),
heroes = roomInfo.elementHeroes(),
)
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.use
class RoomSummaryFactory(
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
private val roomInfoMapper: MatrixRoomInfoMapper = MatrixRoomInfoMapper(),
) {
suspend fun create(roomListItem: RoomListItem): RoomSummary {
val roomInfo = roomListItem.roomInfo().let(roomInfoMapper::map)
val latestRoomMessage = roomListItem.latestEvent().use { event ->
roomMessageFactory.create(event)
}
return RoomSummary(
info = roomInfo,
lastMessage = latestRoomMessage,
)
}
}

View file

@ -23,7 +23,7 @@ class RoomSummaryListProcessor(
private val roomSummaries: MutableSharedFlow<List<RoomSummary>>,
private val roomListService: RoomListServiceInterface,
private val coroutineContext: CoroutineContext,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory(),
) {
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
private val mutex = Mutex()

View file

@ -105,6 +105,10 @@ class MatrixRoomInfoMapperTest {
).toImmutableList(),
pinnedEventIds = listOf(AN_EVENT_ID).toPersistentList(),
creator = A_USER_ID,
isMarkedUnread = false,
numUnreadMessages = 12L,
numUnreadNotifications = 13L,
numUnreadMentions = 14L,
)
)
}
@ -174,6 +178,10 @@ class MatrixRoomInfoMapperTest {
heroes = emptyList<MatrixUser>().toImmutableList(),
pinnedEventIds = emptyList<EventId>().toPersistentList(),
creator = null,
isMarkedUnread = true,
numUnreadMessages = 12L,
numUnreadNotifications = 13L,
numUnreadMentions = 14L,
)
)
}

View file

@ -19,7 +19,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SERVER_LIST
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
@ -29,7 +29,7 @@ import org.junit.Test
class DefaultJoinRoomTest {
@Test
fun `when using roomId and there is no server names, the classic join room API is used`() = runTest {
val roomSummary = aRoomSummaryFilled()
val roomSummary = aRoomSummary()
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()
@ -64,7 +64,7 @@ class DefaultJoinRoomTest {
@Test
fun `when using roomId and server names are available, joinRoomByIdOrAlias API is used`() = runTest {
val roomSummary = aRoomSummaryFilled()
val roomSummary = aRoomSummary()
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()
@ -100,7 +100,7 @@ class DefaultJoinRoomTest {
@Test
fun `when using roomAlias, joinRoomByIdOrAlias API is used`() = runTest {
val roomSummary = aRoomSummaryFilled()
val roomSummary = aRoomSummary()
val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) }
val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List<String> -> Result.success(roomSummary) }
val roomResult = FakeMatrixRoom()

View file

@ -16,10 +16,11 @@ import org.junit.Test
class RoomListFilterTest {
private val regularRoom = aRoomSummary(
isDm = false
isDirect = false,
)
private val dmRoom = aRoomSummary(
isDm = true
isDirect = true,
activeMembersCount = 2
)
private val favoriteRoom = aRoomSummary(
isFavorite = true

View file

@ -15,7 +15,6 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@ -40,7 +39,7 @@ class RoomSummaryListProcessorTest {
@Test
fun `PushBack adds a new entry at the end of the list`() = runTest {
summaries.value = listOf(aRoomSummaryFilled())
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
processor.postUpdate(listOf(RoomListEntriesUpdate.PushBack(FakeRustRoomListItem(A_ROOM_ID_2))))
@ -50,7 +49,7 @@ class RoomSummaryListProcessorTest {
@Test
fun `PushFront inserts a new entry at the start of the list`() = runTest {
summaries.value = listOf(aRoomSummaryFilled())
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
processor.postUpdate(listOf(RoomListEntriesUpdate.PushFront(FakeRustRoomListItem(A_ROOM_ID_2))))
@ -60,7 +59,7 @@ class RoomSummaryListProcessorTest {
@Test
fun `Set replaces an entry at some index`() = runTest {
summaries.value = listOf(aRoomSummaryFilled())
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
val index = 0
@ -72,7 +71,7 @@ class RoomSummaryListProcessorTest {
@Test
fun `Insert inserts a new entry at the provided index`() = runTest {
summaries.value = listOf(aRoomSummaryFilled())
summaries.value = listOf(aRoomSummary())
val processor = createProcessor()
val index = 0
@ -84,7 +83,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `Remove removes an entry at some index`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
val index = 0
@ -96,7 +98,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `PopBack removes an entry at the end of the list`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
val index = 0
@ -108,7 +113,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `PopFront removes an entry at the start of the list`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
val index = 0
@ -120,7 +128,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `Clear removes all the entries`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
processor.postUpdate(listOf(RoomListEntriesUpdate.Clear))
@ -130,7 +141,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `Truncate removes all entries after the provided length`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
val index = 0
@ -142,7 +156,10 @@ class RoomSummaryListProcessorTest {
@Test
fun `Reset removes all entries and add the provided ones`() = runTest {
summaries.value = listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(A_ROOM_ID_2))
summaries.value = listOf(
aRoomSummary(roomId = A_ROOM_ID),
aRoomSummary(A_ROOM_ID_2)
)
val processor = createProcessor()
val index = 0
@ -156,6 +173,6 @@ class RoomSummaryListProcessorTest {
summaries,
FakeRustRoomListService(),
coroutineContext = StandardTestDispatcher(testScheduler),
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
roomSummaryDetailsFactory = RoomSummaryFactory(),
)
}

View file

@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
@ -31,7 +30,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.location.AssetType
@ -39,21 +37,15 @@ import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerL
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -508,62 +500,6 @@ class FakeMatrixRoom(
}
}
fun aRoomInfo(
id: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
rawName: String? = name,
topic: String? = "A topic",
avatarUrl: String? = AN_AVATAR_URL,
isDirect: Boolean = false,
isPublic: Boolean = true,
isSpace: Boolean = false,
isTombstoned: Boolean = false,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
inviter: RoomMember? = null,
activeMembersCount: Long = 1,
invitedMembersCount: Long = 0,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
activeRoomCallParticipants: List<UserId> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
roomCreator: UserId? = null,
) = MatrixRoomInfo(
id = id,
name = name,
rawName = rawName,
topic = topic,
avatarUrl = avatarUrl,
isDirect = isDirect,
isPublic = isPublic,
isSpace = isSpace,
isTombstoned = isTombstoned,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toImmutableList(),
currentUserMembership = currentUserMembership,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
userPowerLevels = userPowerLevels,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(),
creator = roomCreator,
)
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
ban = 50,
invite = 0,

View file

@ -0,0 +1,90 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
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.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
fun aRoomInfo(
id: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
rawName: String? = A_ROOM_RAW_NAME,
topic: String? = A_ROOM_TOPIC,
avatarUrl: String? = AN_AVATAR_URL,
isDirect: Boolean = false,
isPublic: Boolean = true,
isSpace: Boolean = false,
isTombstoned: Boolean = false,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
inviter: RoomMember? = null,
activeMembersCount: Long = 1,
invitedMembersCount: Long = 0,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
activeRoomCallParticipants: List<UserId> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
roomCreator: UserId? = null,
isMarkedUnread: Boolean = false,
numUnreadMessages: Long = 0,
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
) = MatrixRoomInfo(
id = id,
name = name,
rawName = rawName,
topic = topic,
avatarUrl = avatarUrl,
isDirect = isDirect,
isPublic = isPublic,
isSpace = isSpace,
isTombstoned = isTombstoned,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toImmutableList(),
currentUserMembership = currentUserMembership,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
userPowerLevels = userPowerLevels,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(),
creator = roomCreator,
isMarkedUnread = isMarkedUnread,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
)

View file

@ -12,6 +12,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
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.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.message.RoomMessage
@ -21,69 +22,88 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList
fun aRoomSummaryFilled(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,
isDirect: Boolean = false,
avatarUrl: String? = null,
fun aRoomSummary(
info: MatrixRoomInfo = aRoomInfo(),
lastMessage: RoomMessage? = aRoomMessage(),
numUnreadMentions: Int = 0,
numUnreadMessages: Int = 0,
notificationMode: RoomNotificationMode? = null,
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
) = aRoomSummary(
roomId = roomId,
name = name,
isDirect = isDirect,
avatarUrl = avatarUrl,
) = RoomSummary(
info = info,
lastMessage = lastMessage,
numUnreadMentions = numUnreadMentions,
numUnreadMessages = numUnreadMessages,
notificationMode = notificationMode,
currentUserMembership = currentUserMembership,
)
fun aRoomSummary(
roomId: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
isDirect: Boolean = false,
rawName: String? = A_ROOM_RAW_NAME,
topic: String? = A_ROOM_TOPIC,
avatarUrl: String? = null,
lastMessage: RoomMessage? = aRoomMessage(),
numUnreadMentions: Int = 0,
numUnreadMessages: Int = 0,
numUnreadNotifications: Int = 0,
isMarkedUnread: Boolean = false,
notificationMode: RoomNotificationMode? = null,
inviter: RoomMember? = null,
isDirect: Boolean = false,
isPublic: Boolean = true,
isSpace: Boolean = false,
isTombstoned: Boolean = false,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
hasRoomCall: Boolean = false,
isDm: Boolean = false,
isFavorite: Boolean = false,
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
inviter: RoomMember? = null,
activeMembersCount: Long = 1,
invitedMembersCount: Long = 0,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
activeRoomCallParticipants: List<UserId> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
roomCreator: UserId? = null,
isMarkedUnread: Boolean = false,
numUnreadMessages: Long = 0,
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
lastMessage: RoomMessage? = aRoomMessage(),
) = RoomSummary(
roomId = roomId,
name = name,
isDirect = isDirect,
avatarUrl = avatarUrl,
info = MatrixRoomInfo(
id = roomId,
name = name,
rawName = rawName,
topic = topic,
avatarUrl = avatarUrl,
isDirect = isDirect,
isPublic = isPublic,
isSpace = isSpace,
isTombstoned = isTombstoned,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toPersistentList(),
currentUserMembership = currentUserMembership,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
userPowerLevels = userPowerLevels,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
activeRoomCallParticipants = activeRoomCallParticipants.toPersistentList(),
heroes = heroes.toPersistentList(),
pinnedEventIds = pinnedEventIds.toPersistentList(),
creator = roomCreator,
isMarkedUnread = isMarkedUnread,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
),
lastMessage = lastMessage,
numUnreadMentions = numUnreadMentions,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
isMarkedUnread = isMarkedUnread,
userDefinedNotificationMode = notificationMode,
inviter = inviter,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
hasRoomCall = hasRoomCall,
isDm = isDm,
isFavorite = isFavorite,
currentUserMembership = currentUserMembership,
heroes = heroes,
)
fun aRoomMessage(

View file

@ -1,66 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.ui.components
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.user.MatrixUser
open class RoomSummaryDetailsProvider : PreviewParameterProvider<RoomSummary> {
override val values: Sequence<RoomSummary>
get() = sequenceOf(
aRoomSummaryDetails(),
aRoomSummaryDetails(name = null),
)
}
fun aRoomSummaryDetails(
roomId: RoomId = RoomId("!room:domain"),
name: String? = "roomName",
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
isDirect: Boolean = true,
avatarUrl: String? = null,
lastMessage: RoomMessage? = null,
inviter: RoomMember? = null,
notificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
isDm: Boolean = false,
numUnreadMentions: Int = 0,
numUnreadMessages: Int = 0,
numUnreadNotifications: Int = 0,
isMarkedUnread: Boolean = false,
isFavorite: Boolean = false,
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
heroes: List<MatrixUser> = emptyList(),
) = RoomSummary(
roomId = roomId,
name = name,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases,
isDirect = isDirect,
avatarUrl = avatarUrl,
lastMessage = lastMessage,
inviter = inviter,
userDefinedNotificationMode = notificationMode,
hasRoomCall = hasRoomCall,
isDm = isDm,
numUnreadMentions = numUnreadMentions,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
isMarkedUnread = isMarkedUnread,
isFavorite = isFavorite,
currentUserMembership = currentUserMembership,
heroes = heroes,
)

View file

@ -0,0 +1,39 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.ui.components
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
class SelectRoomInfoProvider : PreviewParameterProvider<SelectRoomInfo> {
override val values: Sequence<SelectRoomInfo>
get() = sequenceOf(
aSelectRoomInfo(roomId = RoomId("!room1:domain")),
aSelectRoomInfo(roomId = RoomId("!room2:domain"), name = "Room with a name"),
aSelectRoomInfo(roomId = RoomId("!room3:domain"), name = "Room with a name and alias", canonicalAlias = RoomAlias("#alias:domain")),
)
}
fun aSelectRoomInfo(
roomId: RoomId,
name: String? = null,
canonicalAlias: RoomAlias? = null,
avatarUrl: String? = null,
heroes: ImmutableList<MatrixUser> = persistentListOf(),
) = SelectRoomInfo(
roomId = roomId,
name = name,
canonicalAlias = canonicalAlias,
avatarUrl = avatarUrl,
heroes = heroes,
)

View file

@ -34,15 +34,15 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toImmutableList
@Composable
fun SelectedRoom(
roomSummary: RoomSummary,
onRemoveRoom: (RoomSummary) -> Unit,
roomInfo: SelectRoomInfo,
onRemoveRoom: (SelectRoomInfo) -> Unit,
modifier: Modifier = Modifier,
) {
Box(
@ -53,14 +53,12 @@ fun SelectedRoom(
horizontalAlignment = Alignment.CenterHorizontally,
) {
CompositeAvatar(
avatarData = roomSummary.getAvatarData(size = AvatarSize.SelectedRoom),
heroes = roomSummary.heroes.map { user ->
user.getAvatarData(size = AvatarSize.SelectedRoom)
}.toImmutableList()
avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom),
heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(),
)
Text(
// If name is null, we do not have space to render "No room name", so just use `#` here.
text = roomSummary.name ?: "#",
text = roomInfo.name ?: "#",
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = MaterialTheme.typography.bodyLarge,
@ -69,14 +67,14 @@ fun SelectedRoom(
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.clip(CircleShape)
.size(20.dp)
.align(Alignment.TopEnd)
.clickable(
indication = ripple(),
interactionSource = remember { MutableInteractionSource() },
onClick = { onRemoveRoom(roomSummary) }
),
.clip(CircleShape)
.size(20.dp)
.align(Alignment.TopEnd)
.clickable(
indication = ripple(),
interactionSource = remember { MutableInteractionSource() },
onClick = { onRemoveRoom(roomInfo) }
),
) {
Icon(
imageVector = CompoundIcons.Close(),
@ -91,10 +89,10 @@ fun SelectedRoom(
@PreviewsDayNight
@Composable
internal fun SelectedRoomPreview(
@PreviewParameter(RoomSummaryDetailsProvider::class) roomSummary: RoomSummary
@PreviewParameter(SelectRoomInfoProvider::class) roomInfo: SelectRoomInfo
) = ElementPreview {
SelectedRoom(
roomSummary = roomSummary,
roomInfo = roomInfo,
onRemoveRoom = {},
)
}

View file

@ -9,10 +9,10 @@ package io.element.android.libraries.matrix.ui.model
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
fun RoomSummary.getAvatarData(size: AvatarSize) = AvatarData(
id = roomId.value,
fun MatrixRoomInfo.getAvatarData(size: AvatarSize) = AvatarData(
id = id.value,
name = name,
url = avatarUrl,
size = size,

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.ui.model
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class SelectRoomInfo(
val roomId: RoomId,
val name: String?,
val canonicalAlias: RoomAlias?,
val avatarUrl: String?,
val heroes: ImmutableList<MatrixUser>,
) {
fun getAvatarData(size: AvatarSize) = AvatarData(
id = roomId.value,
name = name,
url = avatarUrl,
size = size,
)
}

View file

@ -7,10 +7,10 @@
package io.element.android.libraries.roomselect.impl
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
sealed interface RoomSelectEvents {
data class SetSelectedRoom(val room: RoomSummary) : RoomSelectEvents
data class SetSelectedRoom(val room: SelectRoomInfo) : RoomSelectEvents
// TODO remove to restore multi-selection
data object RemoveSelectedRoom : RoomSelectEvents

View file

@ -20,7 +20,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import io.element.android.libraries.roomselect.api.RoomSelectMode
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -36,7 +36,7 @@ class RoomSelectPresenter @AssistedInject constructor(
@Composable
override fun present(): RoomSelectState {
var selectedRooms by remember { mutableStateOf(persistentListOf<RoomSummary>()) }
var selectedRooms by remember { mutableStateOf(persistentListOf<SelectRoomInfo>()) }
var searchQuery by remember { mutableStateOf("") }
var isSearchActive by remember { mutableStateOf(false) }
@ -48,7 +48,7 @@ class RoomSelectPresenter @AssistedInject constructor(
dataSource.setSearchQuery(searchQuery)
}
val roomSummaryDetailsList by dataSource.roomSummaries.collectAsState(initial = persistentListOf())
val roomSummaryDetailsList by dataSource.roomInfoList.collectAsState(initial = persistentListOf())
val searchResults by remember {
derivedStateOf {

View file

@ -12,8 +12,8 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.coroutineScope
@ -38,11 +38,20 @@ class RoomSelectSearchDataSource @Inject constructor(
source = RoomList.Source.All,
)
val roomSummaries: Flow<PersistentList<RoomSummary>> = roomList.filteredSummaries
val roomInfoList: Flow<PersistentList<SelectRoomInfo>> = roomList.filteredSummaries
.map { roomSummaries ->
roomSummaries
.filter { it.currentUserMembership == CurrentUserMembership.JOINED }
.filter { it.info.currentUserMembership == CurrentUserMembership.JOINED }
.distinctBy { it.roomId } // This should be removed once we're sure no duplicate Rooms can be received
.map { roomSummary ->
SelectRoomInfo(
roomId = roomSummary.roomId,
name = roomSummary.info.name,
avatarUrl = roomSummary.info.avatarUrl,
heroes = roomSummary.info.heroes,
canonicalAlias = roomSummary.info.canonicalAlias,
)
}
.toPersistentList()
}
.flowOn(coroutineDispatchers.computation)

View file

@ -8,15 +8,15 @@
package io.element.android.libraries.roomselect.impl
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import io.element.android.libraries.roomselect.api.RoomSelectMode
import kotlinx.collections.immutable.ImmutableList
data class RoomSelectState(
val mode: RoomSelectMode,
val resultState: SearchBarResultState<ImmutableList<RoomSummary>>,
val resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>>,
val query: String,
val isSearchActive: Boolean,
val selectedRooms: ImmutableList<RoomSummary>,
val selectedRooms: ImmutableList<SelectRoomInfo>,
val eventSink: (RoomSelectEvents) -> Unit
)

View file

@ -11,8 +11,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
import io.element.android.libraries.matrix.ui.components.aSelectRoomInfo
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import io.element.android.libraries.roomselect.api.RoomSelectMode
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -32,7 +32,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> {
resultState = SearchBarResultState.Results(aRoomSelectRoomList()),
query = "Test",
isSearchActive = true,
selectedRooms = persistentListOf(aRoomSummaryDetails(roomId = RoomId("!room2:domain")))
selectedRooms = aRoomSelectRoomList().subList(0, 1),
),
aRoomSelectState(
mode = RoomSelectMode.Share,
@ -43,10 +43,10 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> {
private fun aRoomSelectState(
mode: RoomSelectMode = RoomSelectMode.Forward,
resultState: SearchBarResultState<ImmutableList<RoomSummary>> = SearchBarResultState.Initial(),
resultState: SearchBarResultState<ImmutableList<SelectRoomInfo>> = SearchBarResultState.Initial(),
query: String = "",
isSearchActive: Boolean = false,
selectedRooms: ImmutableList<RoomSummary> = persistentListOf(),
selectedRooms: ImmutableList<SelectRoomInfo> = persistentListOf(),
) = RoomSelectState(
mode = mode,
resultState = resultState,
@ -57,14 +57,16 @@ private fun aRoomSelectState(
)
private fun aRoomSelectRoomList() = persistentListOf(
aRoomSummaryDetails(),
aRoomSummaryDetails(
aSelectRoomInfo(
roomId = RoomId("!room1:domain"),
name = "Room with name",
),
aSelectRoomInfo(
roomId = RoomId("!room2:domain"),
name = "Room with alias",
canonicalAlias = RoomAlias("#alias:example.org"),
),
aRoomSummaryDetails(
aSelectRoomInfo(
roomId = RoomId("!room3:domain"),
name = null,
),
)

View file

@ -47,8 +47,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.components.SelectedRoom
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.roomselect.api.RoomSelectMode
import io.element.android.libraries.ui.strings.CommonStrings
@ -65,13 +65,13 @@ fun RoomSelectView(
modifier: Modifier = Modifier,
) {
@Suppress("UNUSED_PARAMETER")
fun onRoomRemoved(roomSummary: RoomSummary) {
fun onRoomRemoved(roomInfo: SelectRoomInfo) {
// TODO toggle selection when multi-selection is enabled
state.eventSink(RoomSelectEvents.RemoveSelectedRoom)
}
@Composable
fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList<RoomSummary>) {
fun SelectedRoomsHelper(isForwarding: Boolean, selectedRooms: ImmutableList<SelectRoomInfo>) {
if (isForwarding) return
SelectedRooms(
selectedRooms = selectedRooms,
@ -185,8 +185,8 @@ fun RoomSelectView(
@Composable
private fun SelectedRooms(
selectedRooms: ImmutableList<RoomSummary>,
onRemoveRoom: (RoomSummary) -> Unit,
selectedRooms: ImmutableList<SelectRoomInfo>,
onRemoveRoom: (SelectRoomInfo) -> Unit,
modifier: Modifier = Modifier,
) {
LazyRow(
@ -194,29 +194,29 @@ private fun SelectedRooms(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(32.dp)
) {
items(selectedRooms, key = { it.roomId.value }) { roomSummary ->
SelectedRoom(roomSummary = roomSummary, onRemoveRoom = onRemoveRoom)
items(selectedRooms, key = { it.roomId.value }) { selectRoomInfo ->
SelectedRoom(roomInfo = selectRoomInfo, onRemoveRoom = onRemoveRoom)
}
}
}
@Composable
private fun RoomSummaryView(
summary: RoomSummary,
roomInfo: SelectRoomInfo,
isSelected: Boolean,
onSelection: (RoomSummary) -> Unit,
onSelection: (SelectRoomInfo) -> Unit,
) {
Row(
modifier = Modifier
.clickable { onSelection(summary) }
.clickable { onSelection(roomInfo) }
.fillMaxWidth()
.padding(start = 16.dp, end = 4.dp)
.heightIn(56.dp),
verticalAlignment = Alignment.CenterVertically
) {
CompositeAvatar(
avatarData = summary.getAvatarData(size = AvatarSize.RoomSelectRoomListItem),
heroes = summary.heroes.map { user ->
avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomSelectRoomListItem),
heroes = roomInfo.heroes.map { user ->
user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem)
}.toPersistentList()
)
@ -228,14 +228,14 @@ private fun RoomSummaryView(
// Name
Text(
style = ElementTheme.typography.fontBodyLgRegular,
text = summary.name ?: stringResource(id = CommonStrings.common_no_room_name),
fontStyle = FontStyle.Italic.takeIf { summary.name == null },
text = roomInfo.name ?: stringResource(id = CommonStrings.common_no_room_name),
fontStyle = FontStyle.Italic.takeIf { roomInfo.name == null },
color = ElementTheme.colors.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Alias
summary.canonicalAlias?.let { alias ->
roomInfo.canonicalAlias?.let { alias ->
Text(
text = alias.value,
color = ElementTheme.colors.textSecondary,
@ -245,7 +245,7 @@ private fun RoomSummaryView(
)
}
}
RadioButton(selected = isSelected, onClick = { onSelection(summary) })
RadioButton(selected = isSelected, onClick = { onSelection(roomInfo) })
}
}

View file

@ -14,8 +14,10 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.ui.components.aSelectRoomInfo
import io.element.android.libraries.roomselect.api.RoomSelectMode
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.testCoroutineDispatchers
@ -58,8 +60,9 @@ class RoomSelectPresenterTest {
@Test
fun `present - update query`() = runTest {
val roomSummary = aRoomSummary()
val roomListService = FakeRoomListService().apply {
postAllRooms(listOf(aRoomSummary()))
postAllRooms(listOf(roomSummary))
}
val presenter = createRoomSelectPresenter(
roomListService = roomListService
@ -68,19 +71,10 @@ class RoomSelectPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
val expectedRoomSummary = aRoomSummary()
val expectedRoomInfo = roomSummary.toSelectRoomInfo()
// Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result
val result = (awaitItem().resultState as SearchBarResultState.Results).results.map { roomSummary ->
roomSummary.copy(
lastMessage = roomSummary.lastMessage!!.copy(
event = roomSummary.lastMessage!!.event.copy(
debugInfoProvider = expectedRoomSummary.lastMessage!!.event.debugInfoProvider,
messageShieldProvider = expectedRoomSummary.lastMessage!!.event.messageShieldProvider,
)
),
)
}
assertThat(result).isEqualTo(listOf(expectedRoomSummary))
val result = (awaitItem().resultState as SearchBarResultState.Results).results
assertThat(result).isEqualTo(listOf(expectedRoomInfo))
initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
skipItems(1)
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
@ -99,8 +93,9 @@ class RoomSelectPresenterTest {
@Test
fun `present - select and remove a room`() = runTest {
val roomSummary = aRoomSummary()
val roomListService = FakeRoomListService().apply {
postAllRooms(listOf(aRoomSummary()))
postAllRooms(listOf(roomSummary))
}
val presenter = createRoomSelectPresenter(
roomListService = roomListService,
@ -109,9 +104,9 @@ class RoomSelectPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
val summary = aRoomSummary()
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(summary))
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(summary))
val roomInfo = roomSummary.toSelectRoomInfo()
initialState.eventSink(RoomSelectEvents.SetSelectedRoom(roomInfo))
assertThat(awaitItem().selectedRooms).isEqualTo(persistentListOf(roomInfo))
initialState.eventSink(RoomSelectEvents.RemoveSelectedRoom)
assertThat(awaitItem().selectedRooms).isEmpty()
cancel()
@ -128,4 +123,12 @@ class RoomSelectPresenterTest {
coroutineDispatchers = testCoroutineDispatchers(),
),
)
private fun RoomSummary.toSelectRoomInfo() = aSelectRoomInfo(
roomId = roomId,
name = info.name,
avatarUrl = info.avatarUrl,
heroes = info.heroes,
canonicalAlias = info.canonicalAlias,
)
}

View file

@ -8,13 +8,27 @@
package io.element.android.libraries.textcomposer.mentions
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
@Immutable
sealed interface ResolvedSuggestion {
data object AtRoom : ResolvedSuggestion
data class Member(val roomMember: RoomMember) : ResolvedSuggestion
data class Alias(val roomAlias: RoomAlias, val roomSummary: RoomSummary) : ResolvedSuggestion
data class Alias(
val roomAlias: RoomAlias,
val roomId: RoomId,
val roomName: String?,
val roomAvatarUrl: String?,
) : ResolvedSuggestion {
fun getAvatarData(size: AvatarSize) = AvatarData(
id = roomId.value,
name = roomName,
url = roomAvatarUrl,
size = size,
)
}
}

View file

@ -16,10 +16,10 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.libraries.textcomposer.mentions.MentionSpan
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
@ -34,7 +34,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertMention - room alias - getMentions return empty list`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
val suggestion = aRoomAliasSuggestion()
val permalinkBuilder = FakePermalinkBuilder()
val mentionSpanProvider = aMentionSpanProvider()
state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
@ -46,7 +46,7 @@ class MarkdownTextEditorStateTest {
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
}
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
val suggestion = aRoomAliasSuggestion()
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.failure(IllegalStateException("Failed")) })
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
@ -58,7 +58,7 @@ class MarkdownTextEditorStateTest {
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
}
val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
val suggestion = aRoomAliasSuggestion()
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/${A_ROOM_ALIAS.value}") })
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
@ -202,4 +202,13 @@ class MarkdownTextEditorStateTest {
}
}
}
private fun aRoomAliasSuggestion(): ResolvedSuggestion.Alias {
return ResolvedSuggestion.Alias(
roomAlias = A_ROOM_ALIAS,
roomId = A_ROOM_ID,
roomName = null,
roomAvatarUrl = null
)
}
}