Merge pull request #2273 from element-hq/feature/bma/roomListRoomSummaryCleanup

Room list room summary cleanup
This commit is contained in:
Benoit Marty
2024-01-22 15:08:22 +01:00
committed by GitHub
24 changed files with 181 additions and 153 deletions

View File

@@ -19,7 +19,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
@@ -71,25 +71,29 @@ internal fun aRoomListState() = RoomListState(
internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
return persistentListOf(
RoomListRoomSummary(
aRoomListRoomSummary(
name = "Room",
hasUnread = true,
timestamp = "14:18",
lastMessage = "A very very very very long message which suites on two lines",
avatarData = AvatarData("!id", "R", size = AvatarSize.RoomListItem),
id = "!roomId:domain",
roomId = RoomId("!roomId:domain")
),
RoomListRoomSummary(
aRoomListRoomSummary(
name = "Room#2",
hasUnread = false,
timestamp = "14:16",
lastMessage = "A short message",
avatarData = AvatarData("!id", "Z", size = AvatarSize.RoomListItem),
id = "!roomId2:domain",
roomId = RoomId("!roomId2:domain")
),
RoomListRoomSummaryPlaceholders.create("!roomId2:domain"),
RoomListRoomSummaryPlaceholders.create("!roomId3:domain"),
aRoomListRoomSummary(
id = "!roomId3:domain",
isPlaceholder = true,
),
aRoomListRoomSummary(
id = "!roomId4:domain",
isPlaceholder = true,
),
)
}

View File

@@ -17,16 +17,9 @@
package io.element.android.features.roomlist.impl.datasource
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
import io.element.android.libraries.androidutils.diff.DiffCacheUpdater
import io.element.android.libraries.androidutils.diff.MutableListDiffCache
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
@@ -49,8 +42,7 @@ import kotlin.time.Duration.Companion.seconds
class RoomListDataSource @Inject constructor(
private val roomListService: RoomListService,
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter,
private val roomListRoomSummaryFactory: RoomListRoomSummaryFactory,
private val coroutineDispatchers: CoroutineDispatchers,
private val notificationSettingsService: NotificationSettingsService,
private val appScope: CoroutineScope,
@@ -121,7 +113,7 @@ class RoomListDataSource @Inject constructor(
private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>) {
if (diffCache.isEmpty()) {
_allRooms.emit(
RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList()
roomListRoomSummaryFactory.createFakeList()
)
} else {
val roomListRoomSummaries = ArrayList<RoomListRoomSummary>()
@@ -141,33 +133,10 @@ class RoomListDataSource @Inject constructor(
private fun buildAndCacheItem(roomSummaries: List<RoomSummary>, index: Int): RoomListRoomSummary? {
val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) {
is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
is RoomSummary.Filled -> {
val avatarData = AvatarData(
id = roomSummary.identifier(),
name = roomSummary.details.name,
url = roomSummary.details.avatarUrl,
size = AvatarSize.RoomListItem,
)
val roomIdentifier = roomSummary.identifier()
RoomListRoomSummary(
id = roomSummary.identifier(),
roomId = RoomId(roomIdentifier),
name = roomSummary.details.name,
hasUnread = roomSummary.details.unreadNotificationCount > 0,
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
lastMessage = roomSummary.details.lastMessage?.let { message ->
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
}.orEmpty(),
avatarData = avatarData,
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
hasRoomCall = roomSummary.details.hasRoomCall,
isDm = roomSummary.details.isDm,
)
}
is RoomSummary.Empty -> roomListRoomSummaryFactory.createPlaceholder(roomSummary.identifier)
is RoomSummary.Filled -> roomListRoomSummaryFactory.create(roomSummary)
null -> null
}
diffCache[index] = roomListRoomSummary
return roomListRoomSummary
}

View File

@@ -0,0 +1,81 @@
/*
* 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.features.roomlist.impl.datasource
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
class RoomListRoomSummaryFactory @Inject constructor(
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter,
) {
fun createPlaceholder(id: String): RoomListRoomSummary {
return RoomListRoomSummary(
id = id,
roomId = RoomId("!aRoom:domain"),
isPlaceholder = true,
name = "Short name",
timestamp = "hh:mm",
lastMessage = "Last message for placeholder",
avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem),
hasUnread = false,
userDefinedNotificationMode = null,
hasRoomCall = false,
isDm = false,
)
}
fun createFakeList(): ImmutableList<RoomListRoomSummary> {
return List(16) {
createPlaceholder("!fakeRoom$it:domain")
}.toImmutableList()
}
fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {
val roomIdentifier = roomSummary.identifier()
val avatarData = AvatarData(
id = roomIdentifier,
name = roomSummary.details.name,
url = roomSummary.details.avatarUrl,
size = AvatarSize.RoomListItem,
)
return RoomListRoomSummary(
id = roomIdentifier,
roomId = RoomId(roomIdentifier),
name = roomSummary.details.name,
hasUnread = roomSummary.details.unreadNotificationCount > 0,
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
lastMessage = roomSummary.details.lastMessage?.let { message ->
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
}.orEmpty(),
avatarData = avatarData,
isPlaceholder = false,
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
hasRoomCall = roomSummary.details.hasRoomCall,
isDm = roomSummary.details.isDm,
)
}
}

View File

@@ -18,7 +18,6 @@ package io.element.android.features.roomlist.impl.model
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.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@@ -26,13 +25,13 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
data class RoomListRoomSummary(
val id: String,
val roomId: RoomId,
val name: String = "",
val hasUnread: Boolean = false,
val timestamp: String? = null,
val lastMessage: CharSequence? = null,
val avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
val isPlaceholder: Boolean = false,
val userDefinedNotificationMode: RoomNotificationMode? = null,
val hasRoomCall: Boolean = false,
val isDm: Boolean = false,
val name: String,
val hasUnread: Boolean,
val timestamp: String?,
val lastMessage: CharSequence?,
val avatarData: AvatarData,
val isPlaceholder: Boolean,
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val isDm: Boolean,
)

View File

@@ -1,43 +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.features.roomlist.impl.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.RoomId
object RoomListRoomSummaryPlaceholders {
fun create(id: String): RoomListRoomSummary {
return RoomListRoomSummary(
id = id,
roomId = RoomId("!aRoom:domain"),
isPlaceholder = true,
name = "Short name",
timestamp = "hh:mm",
lastMessage = "Last message for placeholder",
avatarData = AvatarData(id, "S", size = AvatarSize.RoomListItem)
)
}
fun createFakeList(size: Int): List<RoomListRoomSummary> {
return mutableListOf<RoomListRoomSummary>().apply {
repeat(size) {
add(create("!fakeRoom$it:domain"))
}
}
}
}

View File

@@ -28,10 +28,10 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
aRoomListRoomSummary(),
aRoomListRoomSummary(lastMessage = null),
aRoomListRoomSummary(hasUnread = true, notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
aRoomListRoomSummary(timestamp = "88:88", notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
aRoomListRoomSummary(timestamp = "88:88", notificationMode = RoomNotificationMode.MUTE),
aRoomListRoomSummary(timestamp = "88:88", hasUnread = true),
aRoomListRoomSummary(isPlaceholder = true, timestamp = "88:88"),
aRoomListRoomSummary(notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
aRoomListRoomSummary(notificationMode = RoomNotificationMode.MUTE),
aRoomListRoomSummary(hasUnread = true),
aRoomListRoomSummary(isPlaceholder = true),
aRoomListRoomSummary(
name = "A very long room name that should be truncated",
lastMessage = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" +
@@ -44,23 +44,27 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
)
}
fun aRoomListRoomSummary(
lastMessage: String? = "Last message",
notificationMode: RoomNotificationMode? = null,
hasUnread: Boolean = false,
timestamp: String? = "88:88",
hasRoomCall: Boolean = false,
isPlaceholder: Boolean = false,
internal fun aRoomListRoomSummary(
id: String = "!roomId:domain",
name: String = "Room name",
hasUnread: Boolean = false,
lastMessage: String? = "Last message",
timestamp: String? = lastMessage?.let { "88:88" },
isPlaceholder: Boolean = false,
notificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
isDm: Boolean = false,
) = RoomListRoomSummary(
id = "!roomId",
roomId = RoomId("!roomId:domain"),
id = id,
roomId = RoomId(id),
name = name,
hasUnread = hasUnread,
timestamp = timestamp,
lastMessage = lastMessage,
avatarData = AvatarData("!roomId", "Room name", size = AvatarSize.RoomListItem),
avatarData = avatarData,
isPlaceholder = isPlaceholder,
userDefinedNotificationMode = notificationMode,
hasRoomCall = hasRoomCall,
isDm = isDm,
)

View File

@@ -28,8 +28,8 @@ import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -316,7 +316,7 @@ class RoomListPresenterTests {
skipItems(1)
val initialState = awaitItem()
val summary = aRoomListRoomSummary()
val summary = aRoomListRoomSummary
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
val shownState = awaitItem()
@@ -336,7 +336,7 @@ class RoomListPresenterTests {
skipItems(1)
val initialState = awaitItem()
val summary = aRoomListRoomSummary()
val summary = aRoomListRoomSummary
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
val shownState = awaitItem()
@@ -415,9 +415,11 @@ class RoomListPresenterTests {
inviteStateDataSource = inviteStateDataSource,
leaveRoomPresenter = leaveRoomPresenter,
roomListDataSource = RoomListDataSource(
client.roomListService,
lastMessageTimestampFormatter,
roomLastMessageFormatter,
roomListService = client.roomListService,
roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
lastMessageTimestampFormatter = lastMessageTimestampFormatter,
roomLastMessageFormatter = roomLastMessageFormatter,
),
coroutineDispatchers = testCoroutineDispatchers(),
notificationSettingsService = client.notificationSettingsService(),
appScope = coroutineScope
@@ -443,4 +445,7 @@ private val aRoomListRoomSummary = RoomListRoomSummary(
lastMessage = "",
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
isPlaceholder = false,
userDefinedNotificationMode = null,
hasRoomCall = false,
isDm = false,
)

View File

@@ -27,6 +27,7 @@ import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.dateformatter.impl.DateFormatters
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
@@ -76,12 +77,20 @@ class RoomListScreen(
leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
roomListDataSource = RoomListDataSource(
roomListService = matrixClient.roomListService,
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
sp = stringProvider,
roomMembershipContentFormatter = RoomMembershipContentFormatter(matrixClient, stringProvider),
profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
stateContentFormatter = StateContentFormatter(stringProvider),
roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(
localDateTimeProvider = dateTimeProvider,
dateFormatters = dateFormatters
),
roomLastMessageFormatter = DefaultRoomLastMessageFormatter(
sp = stringProvider,
roomMembershipContentFormatter = RoomMembershipContentFormatter(
matrixClient = matrixClient,
sp = stringProvider
),
profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
stateContentFormatter = StateContentFormatter(stringProvider),
),
),
coroutineDispatchers = coroutineDispatchers,
notificationSettingsService = matrixClient.notificationSettingsService(),