From 2a7a46841ea4599001cdd9e4e97188941392160b Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 14 Sep 2023 14:24:13 +0100 Subject: [PATCH 01/12] Show a room list decoration for notification setting applied - Add the UI - Rebuild room summaries when push rules change or when user disables notifications(hide them all) --- features/roomlist/impl/build.gradle.kts | 1 + .../impl/components/RoomSummaryRow.kt | 46 +++++++++++++++-- .../impl/datasource/RoomListDataSource.kt | 50 ++++++++++++++++--- .../impl/model/RoomListRoomSummary.kt | 2 + .../impl/model/RoomListRoomSummaryProvider.kt | 6 ++- .../libraries/designsystem/VectorIcons.kt | 2 + .../src/main/res/drawable/ic_mention.xml | 13 +++++ .../src/main/res/drawable/ic_mute.xml | 10 ++++ .../matrix/api/roomlist/RoomListService.kt | 5 ++ .../matrix/api/roomlist/RoomSummary.kt | 2 + .../roomlist/RoomSummaryDetailsFactory.kt | 2 + .../impl/roomlist/RoomSummaryListProcessor.kt | 12 +++++ .../impl/roomlist/RustRoomListService.kt | 6 +++ 13 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 libraries/designsystem/src/main/res/drawable/ic_mention.xml create mode 100644 libraries/designsystem/src/main/res/drawable/ic_mute.xml diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index f5a08ba860..3a3639de98 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.eventformatter.api) implementation(projects.libraries.deeplink) + implementation(projects.libraries.pushstore.api) implementation(projects.features.invitelist.api) implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index aab174c6a4..61826dc2b2 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -19,14 +19,17 @@ package io.element.android.features.roomlist.impl.components import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -38,6 +41,9 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -48,16 +54,20 @@ import androidx.compose.ui.unit.dp import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvider import io.element.android.libraries.core.extensions.orEmpty +import io.element.android.libraries.designsystem.VectorIcons import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.roomListRoomMessage import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate import io.element.android.libraries.designsystem.theme.roomListRoomName import io.element.android.libraries.designsystem.theme.unreadIndicator +import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings internal val minHeight = 84.dp @@ -168,11 +178,39 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { maxLines = 2, overflow = TextOverflow.Ellipsis ) + // Unread - UnreadIndicatorAtom( - modifier = Modifier.padding(top = 3.dp), - isVisible = room.hasUnread, - ) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + NotificationIcon(room) + if (room.hasUnread) { + UnreadIndicatorAtom( + modifier = Modifier.padding(top = 3.dp), + ) + } + } + +} + +@Composable +private fun NotificationIcon(room: RoomListRoomSummary) { + val tint = if(room.hasUnread) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary + when(room.notificationMode) { + null, RoomNotificationMode.ALL_MESSAGES -> return + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> + Icon( + contentDescription = stringResource(CommonStrings.screen_notification_settings_mode_mentions), + imageVector = ImageVector.vectorResource(VectorIcons.Mention), + tint = tint, + ) + RoomNotificationMode.MUTE -> + Icon( + contentDescription = stringResource(CommonStrings.common_mute), + imageVector = ImageVector.vectorResource(VectorIcons.Mute), + tint = tint, + ) + } } val TextPlaceholderShape = PercentRectangleSizeShape(0.5f) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index e44bcd6b6b..e0b20aada9 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -26,30 +26,57 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat 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.MatrixClient 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.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.pushstore.api.UserPushStoreFactory import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds class RoomListDataSource @Inject constructor( private val roomListService: RoomListService, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, private val coroutineDispatchers: CoroutineDispatchers, + notificationSettingsService: NotificationSettingsService, + appScope: CoroutineScope, + userPushStoreFactory: UserPushStoreFactory, + matrixClient: MatrixClient, ) { + init { + notificationSettingsService.notificationSettingsChangeFlow + .debounce(0.5.seconds) + .onEach { + roomListService.rebuildRoomSummaries() + } + .launchIn(appScope) + val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) + userPushStore.getNotificationEnabledForDevice().distinctUntilChanged() + .onEach { + roomListService.rebuildRoomSummaries() + } + .launchIn(appScope) + } private val _filter = MutableStateFlow("") private val _allRooms = MutableStateFlow>(persistentListOf()) private val _filteredRooms = MutableStateFlow>(persistentListOf()) @@ -59,13 +86,15 @@ class RoomListDataSource @Inject constructor( private val diffCacheUpdater = DiffCacheUpdater(diffCache = diffCache, detectMoves = true) { old, new -> old?.identifier() == new?.identifier() } + private val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) fun launchIn(coroutineScope: CoroutineScope) { + roomListService .allRooms() .summaries .onEach { roomSummaries -> - replaceWith(roomSummaries) + replaceWith(roomSummaries, userPushStore.getNotificationEnabledForDevice().first()) } .launchIn(coroutineScope) @@ -92,14 +121,14 @@ class RoomListDataSource @Inject constructor( val allRooms: StateFlow> = _allRooms val filteredRooms: StateFlow> = _filteredRooms - private suspend fun replaceWith(roomSummaries: List) = withContext(coroutineDispatchers.computation) { + private suspend fun replaceWith(roomSummaries: List, notificationsEnabled: Boolean) = withContext(coroutineDispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(roomSummaries) - buildAndEmitAllRooms(roomSummaries) + buildAndEmitAllRooms(roomSummaries, notificationsEnabled) } } - private suspend fun buildAndEmitAllRooms(roomSummaries: List) { + private suspend fun buildAndEmitAllRooms(roomSummaries: List, notificationsEnabled: Boolean) { if (diffCache.isEmpty()) { _allRooms.emit( RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList() @@ -109,7 +138,7 @@ class RoomListDataSource @Inject constructor( for (index in diffCache.indices()) { val cacheItem = diffCache.get(index) if (cacheItem == null) { - buildAndCacheItem(roomSummaries, index)?.also { timelineItemState -> + buildAndCacheItem(roomSummaries, index, notificationsEnabled)?.also { timelineItemState -> roomListRoomSummaries.add(timelineItemState) } } else { @@ -122,11 +151,18 @@ class RoomListDataSource @Inject constructor( private fun buildAndCacheItem( roomSummaries: List, - index: Int + index: Int, + notificationsEnabled: Boolean, ): RoomListRoomSummary? { val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) { is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) is RoomSummary.Filled -> { + // Only show a decoration if notifications are enabled and the mode is not ALL_MESSAGES + val notificationMode = if (roomSummary.details.notificationMode == RoomNotificationMode.ALL_MESSAGES || !notificationsEnabled) { + null + } else { + roomSummary.details.notificationMode + } val avatarData = AvatarData( id = roomSummary.identifier(), name = roomSummary.details.name, @@ -144,10 +180,12 @@ class RoomListDataSource @Inject constructor( roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect) }.orEmpty(), avatarData = avatarData, + notificationMode = notificationMode ) } null -> null } + diffCache[index] = roomListRoomSummary return roomListRoomSummary } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index 8ba6c26c0f..bb3405c2d3 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -20,6 +20,7 @@ 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 @Immutable data class RoomListRoomSummary constructor( @@ -31,4 +32,5 @@ data class RoomListRoomSummary constructor( val lastMessage: CharSequence? = null, val avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem), val isPlaceholder: Boolean = false, + val notificationMode: RoomNotificationMode? = null, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt index cd6ca21106..f2db8a376c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt @@ -20,14 +20,16 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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 open class RoomListRoomSummaryProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomListRoomSummary(), aRoomListRoomSummary().copy(lastMessage = null), - aRoomListRoomSummary().copy(hasUnread = true), - aRoomListRoomSummary().copy(timestamp = "88:88"), + aRoomListRoomSummary().copy(hasUnread = true, notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY), + aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY), + aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MUTE), aRoomListRoomSummary().copy(timestamp = "88:88", hasUnread = true), aRoomListRoomSummary().copy(isPlaceholder = true, timestamp = "88:88"), aRoomListRoomSummary().copy( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt index 20ebb85615..229605e3ea 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/VectorIcons.kt @@ -41,4 +41,6 @@ object VectorIcons { val Quote = R.drawable.ic_quote val Strikethrough = R.drawable.ic_strikethrough val Underline = R.drawable.ic_underline + val Mention = R.drawable.ic_mention + val Mute = R.drawable.ic_mute } diff --git a/libraries/designsystem/src/main/res/drawable/ic_mention.xml b/libraries/designsystem/src/main/res/drawable/ic_mention.xml new file mode 100644 index 0000000000..37f70481e5 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_mention.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/ic_mute.xml b/libraries/designsystem/src/main/res/drawable/ic_mute.xml new file mode 100644 index 0000000000..ea6f842696 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_mute.xml @@ -0,0 +1,10 @@ + + + diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index de7f1f3918..8b85b3fe38 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -54,6 +54,11 @@ interface RoomListService { */ fun updateAllRoomsVisibleRange(range: IntRange) + /** + * Rebuild the room summaries, required when we know some data may have changed. (E.g. room notification settings) + */ + fun rebuildRoomSummaries() + /** * The sync indicator as a flow. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index 87cf2139d6..4638fc6e03 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api.roomlist 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.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.message.RoomMessage sealed interface RoomSummary { @@ -42,4 +43,5 @@ data class RoomSummaryDetails( val lastMessageTimestamp: Long?, val unreadNotificationCount: Int, val inviter: RoomMember? = null, + val notificationMode: RoomNotificationMode? = null, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index d33e2f58fc..f9f63ab271 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper import io.element.android.libraries.matrix.impl.room.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory import org.matrix.rustcomponents.sdk.RoomInfo @@ -39,6 +40,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto lastMessage = latestRoomMessage, lastMessageTimestamp = latestRoomMessage?.originServerTs, inviter = roomInfo.inviter?.let(RoomMemberMapper::map), + notificationMode = roomInfo.notificationMode?.let(RoomNotificationSettingsMapper::mapMode), ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index 03b58ed2ce..9ae10db12d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.parallelMap import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate @@ -59,6 +60,17 @@ class RoomSummaryListProcessor( } } + suspend fun rebuildRoomSummaries() { + updateRoomSummaries { + forEachIndexed { i, summary -> + this[i] = when(summary) { + is RoomSummary.Empty -> summary + is RoomSummary.Filled -> buildAndCacheRoomSummaryForIdentifier(summary.identifier()) + } + } + } + } + private suspend fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { when (update) { is RoomListEntriesUpdate.Append -> { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index a2de2be89b..347e53aa0c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -107,6 +107,12 @@ class RustRoomListService( } } + override fun rebuildRoomSummaries() { + sessionCoroutineScope.launch { + allRoomsListProcessor.rebuildRoomSummaries() + } + } + override val syncIndicator: StateFlow = innerRoomListService.syncIndicator() .map { it.toSyncIndicator() } From 43fab70d3078fa254d91f4ef2d0e757d988586f4 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 14 Sep 2023 16:39:11 +0100 Subject: [PATCH 02/12] Only show green indicator for "All Messages" - Only show green indicator for "All Messages" as mentions doesn't work and we should never see it for muted rooms. - Remove code that tried to reflect the notificationsEnabled setting I mis-undertood the requirements by reading the iOS code. --- features/roomlist/impl/build.gradle.kts | 1 - .../impl/components/RoomSummaryRow.kt | 17 +++++---- .../impl/datasource/RoomListDataSource.kt | 36 +++++-------------- 3 files changed, 18 insertions(+), 36 deletions(-) diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 3a3639de98..f5a08ba860 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -49,7 +49,6 @@ dependencies { implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.eventformatter.api) implementation(projects.libraries.deeplink) - implementation(projects.libraries.pushstore.api) implementation(projects.features.invitelist.api) implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index 61826dc2b2..33a8564efb 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -24,12 +24,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -154,7 +152,7 @@ private fun RowScope.NameAndTimestampRow(room: RoomListRoomSummary) { Text( text = room.timestamp ?: "", style = ElementTheme.typography.fontBodySmRegular, - color = if (room.hasUnread) { + color = if (room.shouldDisplayNotificationAlertDecoration) { ElementTheme.colors.unreadIndicator } else { MaterialTheme.roomListRoomMessageDate() @@ -184,7 +182,7 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { NotificationIcon(room) - if (room.hasUnread) { + if (room.shouldDisplayNotificationAlertDecoration) { UnreadIndicatorAtom( modifier = Modifier.padding(top = 3.dp), ) @@ -192,23 +190,28 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { } } +// We should never show a green dot/icon for mute. Also mentions is not yet supported by the mobile app. +// In some cases a green @ was incorrectly shown when we switch from ALL_MESSAGES to MENTIONS_AND_KEYWORDS_ONLY +// and we don't know whether the room has mentions, just that it has unread. +private val RoomListRoomSummary.shouldDisplayNotificationAlertDecoration get() = hasUnread + && notificationMode != RoomNotificationMode.MUTE + && notificationMode != RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY @Composable private fun NotificationIcon(room: RoomListRoomSummary) { - val tint = if(room.hasUnread) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary when(room.notificationMode) { null, RoomNotificationMode.ALL_MESSAGES -> return RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> Icon( contentDescription = stringResource(CommonStrings.screen_notification_settings_mode_mentions), imageVector = ImageVector.vectorResource(VectorIcons.Mention), - tint = tint, + tint = ElementTheme.colors.iconQuaternary, ) RoomNotificationMode.MUTE -> Icon( contentDescription = stringResource(CommonStrings.common_mute), imageVector = ImageVector.vectorResource(VectorIcons.Mute), - tint = tint, + tint = ElementTheme.colors.iconQuaternary, ) } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index e0b20aada9..2cf28a6913 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -26,24 +26,19 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat 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.MatrixClient 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.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.pushstore.api.UserPushStoreFactory import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex @@ -59,8 +54,6 @@ class RoomListDataSource @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, notificationSettingsService: NotificationSettingsService, appScope: CoroutineScope, - userPushStoreFactory: UserPushStoreFactory, - matrixClient: MatrixClient, ) { init { notificationSettingsService.notificationSettingsChangeFlow @@ -69,13 +62,6 @@ class RoomListDataSource @Inject constructor( roomListService.rebuildRoomSummaries() } .launchIn(appScope) - - val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) - userPushStore.getNotificationEnabledForDevice().distinctUntilChanged() - .onEach { - roomListService.rebuildRoomSummaries() - } - .launchIn(appScope) } private val _filter = MutableStateFlow("") private val _allRooms = MutableStateFlow>(persistentListOf()) @@ -86,15 +72,13 @@ class RoomListDataSource @Inject constructor( private val diffCacheUpdater = DiffCacheUpdater(diffCache = diffCache, detectMoves = true) { old, new -> old?.identifier() == new?.identifier() } - private val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) fun launchIn(coroutineScope: CoroutineScope) { - roomListService .allRooms() .summaries .onEach { roomSummaries -> - replaceWith(roomSummaries, userPushStore.getNotificationEnabledForDevice().first()) + replaceWith(roomSummaries) } .launchIn(coroutineScope) @@ -121,14 +105,14 @@ class RoomListDataSource @Inject constructor( val allRooms: StateFlow> = _allRooms val filteredRooms: StateFlow> = _filteredRooms - private suspend fun replaceWith(roomSummaries: List, notificationsEnabled: Boolean) = withContext(coroutineDispatchers.computation) { + private suspend fun replaceWith(roomSummaries: List) = withContext(coroutineDispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(roomSummaries) - buildAndEmitAllRooms(roomSummaries, notificationsEnabled) + buildAndEmitAllRooms(roomSummaries) } } - private suspend fun buildAndEmitAllRooms(roomSummaries: List, notificationsEnabled: Boolean) { + private suspend fun buildAndEmitAllRooms(roomSummaries: List) { if (diffCache.isEmpty()) { _allRooms.emit( RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList() @@ -138,7 +122,7 @@ class RoomListDataSource @Inject constructor( for (index in diffCache.indices()) { val cacheItem = diffCache.get(index) if (cacheItem == null) { - buildAndCacheItem(roomSummaries, index, notificationsEnabled)?.also { timelineItemState -> + buildAndCacheItem(roomSummaries, index)?.also { timelineItemState -> roomListRoomSummaries.add(timelineItemState) } } else { @@ -149,16 +133,12 @@ class RoomListDataSource @Inject constructor( } } - private fun buildAndCacheItem( - roomSummaries: List, - index: Int, - notificationsEnabled: Boolean, - ): RoomListRoomSummary? { + private fun buildAndCacheItem(roomSummaries: List, index: Int, ): RoomListRoomSummary? { val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) { is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) is RoomSummary.Filled -> { - // Only show a decoration if notifications are enabled and the mode is not ALL_MESSAGES - val notificationMode = if (roomSummary.details.notificationMode == RoomNotificationMode.ALL_MESSAGES || !notificationsEnabled) { + // Only show a decoration if the mode is not ALL_MESSAGES + val notificationMode = if (roomSummary.details.notificationMode == RoomNotificationMode.ALL_MESSAGES) { null } else { roomSummary.details.notificationMode From f0aaa5642558fa096f103bad5ed81ccd31771568 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 14 Sep 2023 19:28:56 +0100 Subject: [PATCH 03/12] Fix test compilation --- .../android/features/roomlist/impl/RoomListPresenterTests.kt | 4 +++- .../libraries/matrix/test/roomlist/FakeRoomListService.kt | 4 ++++ .../io/element/android/samples/minimal/RoomListScreen.kt | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index b7bfd3ecb4..8acaa69830 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -325,7 +325,9 @@ class RoomListPresenterTests { client.roomListService, lastMessageTimestampFormatter, roomLastMessageFormatter, - coroutineDispatchers = testCoroutineDispatchers() + coroutineDispatchers = testCoroutineDispatchers(), + notificationSettingsService = client.notificationSettingsService(), + appScope = this ) ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index 7e0f3e8891..75a91508d0 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -58,6 +58,10 @@ class FakeRoomListService : RoomListService { latestSlidingSyncRange = range } + override fun rebuildRoomSummaries() { + + } + override fun allRooms(): RoomList { return SimpleRoomList( summaries = allRoomSummariesFlow, diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index faaccc9b8e..425888f003 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -78,6 +78,8 @@ class RoomListScreen( stateContentFormatter = StateContentFormatter(stringProvider), ), coroutineDispatchers = coroutineDispatchers, + notificationSettingsService = matrixClient.notificationSettingsService(), + appScope = Singleton.appScope ) ) From 14666998e1955e7db0fc82d3e4e7a26e46e449a0 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 14 Sep 2023 20:31:34 +0100 Subject: [PATCH 04/12] Update RoomListDataSource.kt --- .../impl/datasource/RoomListDataSource.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index 2cf28a6913..ac0b3ca568 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -35,6 +35,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -52,17 +53,13 @@ class RoomListDataSource @Inject constructor( private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, private val coroutineDispatchers: CoroutineDispatchers, - notificationSettingsService: NotificationSettingsService, - appScope: CoroutineScope, + private val notificationSettingsService: NotificationSettingsService, + private val appScope: CoroutineScope, ) { init { - notificationSettingsService.notificationSettingsChangeFlow - .debounce(0.5.seconds) - .onEach { - roomListService.rebuildRoomSummaries() - } - .launchIn(appScope) + observerNotificationSettings() } + private val _filter = MutableStateFlow("") private val _allRooms = MutableStateFlow>(persistentListOf()) private val _filteredRooms = MutableStateFlow>(persistentListOf()) @@ -105,6 +102,16 @@ class RoomListDataSource @Inject constructor( val allRooms: StateFlow> = _allRooms val filteredRooms: StateFlow> = _filteredRooms + @OptIn(FlowPreview::class) + private fun observerNotificationSettings() { + notificationSettingsService.notificationSettingsChangeFlow + .debounce(0.5.seconds) + .onEach { + roomListService.rebuildRoomSummaries() + } + .launchIn(appScope) + } + private suspend fun replaceWith(roomSummaries: List) = withContext(coroutineDispatchers.computation) { lock.withLock { diffCacheUpdater.updateWith(roomSummaries) From b842a201d34801c3f2907dc7e7bd1664da0a8b21 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 15 Sep 2023 10:25:10 +0100 Subject: [PATCH 05/12] Cancel scope in tests. --- .../roomlist/impl/RoomListPresenterTests.kt | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 8acaa69830..a9327f64df 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -53,6 +53,9 @@ import io.element.android.libraries.matrix.test.verification.FakeSessionVerifica import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -66,7 +69,8 @@ class RoomListPresenterTests { @Test fun `present - should start with no user and then load user with success`() = runTest { - val presenter = createRoomListPresenter() + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -77,6 +81,7 @@ class RoomListPresenterTests { Truth.assertThat(withUserState.matrixUser!!.userId).isEqualTo(A_USER_ID) Truth.assertThat(withUserState.matrixUser!!.displayName).isEqualTo(A_USER_NAME) Truth.assertThat(withUserState.matrixUser!!.avatarUrl).isEqualTo(AN_AVATAR_URL) + scope.cancel() } } @@ -86,7 +91,8 @@ class RoomListPresenterTests { userDisplayName = Result.failure(AN_EXCEPTION), userAvatarURLString = Result.failure(AN_EXCEPTION), ) - val presenter = createRoomListPresenter(matrixClient) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -94,12 +100,14 @@ class RoomListPresenterTests { Truth.assertThat(initialState.matrixUser).isNull() val withUserState = awaitItem() Truth.assertThat(withUserState.matrixUser).isNotNull() + scope.cancel() } } @Test fun `present - should filter room with success`() = runTest { - val presenter = createRoomListPresenter() + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -109,8 +117,8 @@ class RoomListPresenterTests { withUserState.eventSink.invoke(RoomListEvents.UpdateFilter("t")) val withFilterState = awaitItem() Truth.assertThat(withFilterState.filter).isEqualTo("t") - cancelAndIgnoreRemainingEvents() + scope.cancel() } } @@ -120,7 +128,8 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomListService = roomListService ) - val presenter = createRoomListPresenter(matrixClient) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -133,6 +142,7 @@ class RoomListPresenterTests { Truth.assertThat(withRoomState.roomList.size).isEqualTo(1) Truth.assertThat(withRoomState.roomList.first()) .isEqualTo(aRoomListRoomSummary) + scope.cancel() } } @@ -142,7 +152,8 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomListService = roomListService ) - val presenter = createRoomListPresenter(matrixClient) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(matrixClient, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -160,6 +171,7 @@ class RoomListPresenterTests { val withNotFilteredRoomState = consumeItemsUntilPredicate { state -> state.filteredRoomList.size == 0 }.last() Truth.assertThat(withNotFilteredRoomState.filter).isEqualTo("tada") Truth.assertThat(withNotFilteredRoomState.filteredRoomList).isEmpty() + scope.cancel() } } @@ -169,7 +181,8 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomListService = roomListService ) - val presenter = createRoomListPresenter(matrixClient) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -200,6 +213,7 @@ class RoomListPresenterTests { Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(129, 279)) cancelAndIgnoreRemainingEvents() + scope.cancel() } } @@ -209,12 +223,14 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomListService = roomListService, ) + val scope = CoroutineScope(context = coroutineContext + SupervisorJob()) val presenter = createRoomListPresenter( client = matrixClient, sessionVerificationService = FakeSessionVerificationService().apply { givenIsReady(true) givenVerifiedStatus(SessionVerifiedStatus.NotVerified) }, + coroutineScope = scope, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -224,6 +240,7 @@ class RoomListPresenterTests { eventSink(RoomListEvents.DismissRequestVerificationPrompt) Truth.assertThat(awaitItem().displayVerificationPrompt).isFalse() + scope.cancel() } } @@ -231,7 +248,8 @@ class RoomListPresenterTests { fun `present - sets invite state`() = runTest { val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites) val inviteStateDataSource = FakeInviteDataSource(inviteStateFlow) - val presenter = createRoomListPresenter(inviteStateDataSource = inviteStateDataSource) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(inviteStateDataSource = inviteStateDataSource, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -246,12 +264,14 @@ class RoomListPresenterTests { inviteStateFlow.value = InvitesState.NoInvites Truth.assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites) + scope.cancel() } } @Test fun `present - show context menu`() = runTest { - val presenter = createRoomListPresenter() + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -264,12 +284,14 @@ class RoomListPresenterTests { val shownState = awaitItem() Truth.assertThat(shownState.contextMenu) .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name)) + scope.cancel() } } @Test fun `present - hide context menu`() = runTest { - val presenter = createRoomListPresenter() + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -286,13 +308,15 @@ class RoomListPresenterTests { val hiddenState = awaitItem() Truth.assertThat(hiddenState.contextMenu).isEqualTo(RoomListState.ContextMenu.Hidden) + scope.cancel() } } @Test fun `present - leave room calls into leave room presenter`() = runTest { val leaveRoomPresenter = LeaveRoomPresenterFake() - val presenter = createRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -300,6 +324,7 @@ class RoomListPresenterTests { initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID)) Truth.assertThat(leaveRoomPresenter.events).containsExactly(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID)) cancelAndIgnoreRemainingEvents() + scope.cancel() } } @@ -313,7 +338,8 @@ class RoomListPresenterTests { lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply { givenFormat(A_FORMATTED_DATE) }, - roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter() + roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), + coroutineScope: CoroutineScope = this ) = RoomListPresenter( client = client, sessionVerificationService = sessionVerificationService, @@ -327,7 +353,7 @@ class RoomListPresenterTests { roomLastMessageFormatter, coroutineDispatchers = testCoroutineDispatchers(), notificationSettingsService = client.notificationSettingsService(), - appScope = this + appScope = coroutineScope ) ) } From b87e88d2f272c929b19777479ca0fb885dd948b2 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 15 Sep 2023 10:42:54 +0100 Subject: [PATCH 06/12] Use userDefinedNotificationMode. --- .../libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index f9f63ab271..a9d46296db 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -40,7 +40,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto lastMessage = latestRoomMessage, lastMessageTimestamp = latestRoomMessage?.originServerTs, inviter = roomInfo.inviter?.let(RoomMemberMapper::map), - notificationMode = roomInfo.notificationMode?.let(RoomNotificationSettingsMapper::mapMode), + notificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode), ) } } From bd9f67df21f00ba56a109cedff31a91e6f6060c6 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 15 Sep 2023 10:01:18 +0000 Subject: [PATCH 07/12] Update screenshots --- ...nents_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...nents_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...nents_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...nents_null_RoomSummaryRowDark_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...nents_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...nents_null_RoomSummaryRowDark_0_null_7,NEXUS_5,1.0,en].png | 3 +++ ...ents_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_7,NEXUS_5,1.0,en].png | 3 +++ 12 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_7,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png index 5d673a1cff..e35b511282 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e710857b4a1c3da12ceb51e7400229d0fd7f42d6d6158a07e2c2814c728abfdc -size 12229 +oid sha256:1dd44adb59ebe1ac95bc9483c61d09cc2b59307b0e2f86883f1639ae35819b0f +size 12544 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png index d0ec7b4cb1..7d20bddd45 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db014e76a88015053b1be458ac758dde99f8cf01a7a78850c0bf6ff4db27dcbc -size 13173 +oid sha256:6ca678d3cb26b3224c1fe44169eebd0a96ddcc344e2d9d45ff84c41116c03a0e +size 13829 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png index 606a183dce..b27b7924e2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d062feda6fc808aa87df5a5f4b3229153f7e50925e3c55a8c8a99a583212857 -size 13458 +oid sha256:8618c1af337c98cbd812632fc6dbb468b64a3b53e2141a7073a5ea52656ddaa7 +size 13611 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_5,NEXUS_5,1.0,en].png index 805e00c2b7..606a183dce 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cd7f6969aa48fdde75e1ad046d595882ebdadfc310cd6b6753f43895aec7b99 -size 6281 +oid sha256:4d062feda6fc808aa87df5a5f4b3229153f7e50925e3c55a8c8a99a583212857 +size 13458 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png index 7078257343..805e00c2b7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79b24ca5c6426aa966024d79bdbbc4a612169037cf6bda7496299ea298fa7517 -size 21951 +oid sha256:7cd7f6969aa48fdde75e1ad046d595882ebdadfc310cd6b6753f43895aec7b99 +size 6281 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7078257343 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79b24ca5c6426aa966024d79bdbbc4a612169037cf6bda7496299ea298fa7517 +size 21951 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png index e471eae254..ec64e498e6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c9b33e480bfad9c55d55e25cef2a0ab1f25235382e1b53c93107a75e95fa90e -size 11840 +oid sha256:f3d6f98e42089d2b8b1992a3aeb8c1c91847c3975d3759c00dc75bc85fb0ba5d +size 12195 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png index 582d30fdfc..d08e94a349 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:824e708f277ea66a161f17cb07a3070e03ddcfc326b9dd73d0ad6b7e62da32c1 -size 12807 +oid sha256:6f6bfe205fe096afe6d976ed242d823d02d06a01b8f81e5feca7b9a71d7b5767 +size 13543 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png index 0de6b64bec..4a51acc8ba 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39b7b8354b98bca791698ad65f4e21619136f446b2fd050c302bdd27ce138a4a -size 13263 +oid sha256:8337468ce85f5f1aa9fec867d9037b71c7460fa823c7f6fdcbf0efe48ad97d40 +size 13293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_5,NEXUS_5,1.0,en].png index 6bfb7eeabe..0de6b64bec 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c978bc799ab79290f89568de692d3ace219a4192ec9706fef63fe977a853f74d -size 6046 +oid sha256:39b7b8354b98bca791698ad65f4e21619136f446b2fd050c302bdd27ce138a4a +size 13263 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png index fcf8b20fdc..6bfb7eeabe 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64e7ca21b0213413ed45d0640295ceb2764b01b2bce20a01518e55e620925086 -size 22193 +oid sha256:c978bc799ab79290f89568de692d3ace219a4192ec9706fef63fe977a853f74d +size 6046 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..fcf8b20fdc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64e7ca21b0213413ed45d0640295ceb2764b01b2bce20a01518e55e620925086 +size 22193 From b644c40398c6b8a0b698d9da0a089fd4b2ee58ca Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 15 Sep 2023 15:47:16 +0100 Subject: [PATCH 08/12] Reverting the change to only show the green dot for all messages. --- .../roomlist/impl/components/RoomSummaryRow.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index 33a8564efb..4cd546f6f4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -152,7 +152,7 @@ private fun RowScope.NameAndTimestampRow(room: RoomListRoomSummary) { Text( text = room.timestamp ?: "", style = ElementTheme.typography.fontBodySmRegular, - color = if (room.shouldDisplayNotificationAlertDecoration) { + color = if (room.hasUnread) { ElementTheme.colors.unreadIndicator } else { MaterialTheme.roomListRoomMessageDate() @@ -182,7 +182,7 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { NotificationIcon(room) - if (room.shouldDisplayNotificationAlertDecoration) { + if (room.hasUnread) { UnreadIndicatorAtom( modifier = Modifier.padding(top = 3.dp), ) @@ -190,28 +190,23 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { } } -// We should never show a green dot/icon for mute. Also mentions is not yet supported by the mobile app. -// In some cases a green @ was incorrectly shown when we switch from ALL_MESSAGES to MENTIONS_AND_KEYWORDS_ONLY -// and we don't know whether the room has mentions, just that it has unread. -private val RoomListRoomSummary.shouldDisplayNotificationAlertDecoration get() = hasUnread - && notificationMode != RoomNotificationMode.MUTE - && notificationMode != RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY @Composable private fun NotificationIcon(room: RoomListRoomSummary) { + val tint = if(room.hasUnread) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary when(room.notificationMode) { null, RoomNotificationMode.ALL_MESSAGES -> return RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> Icon( contentDescription = stringResource(CommonStrings.screen_notification_settings_mode_mentions), imageVector = ImageVector.vectorResource(VectorIcons.Mention), - tint = ElementTheme.colors.iconQuaternary, + tint = tint, ) RoomNotificationMode.MUTE -> Icon( contentDescription = stringResource(CommonStrings.common_mute), imageVector = ImageVector.vectorResource(VectorIcons.Mute), - tint = ElementTheme.colors.iconQuaternary, + tint = tint, ) } } From 0f32c2dfaebd6d7428abc5a56ee317c8c25ee9c7 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 15 Sep 2023 15:48:51 +0000 Subject: [PATCH 09/12] Update screenshots --- ...nents_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ents_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png index e35b511282..4ad70e1e7d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowDark_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1dd44adb59ebe1ac95bc9483c61d09cc2b59307b0e2f86883f1639ae35819b0f -size 12544 +oid sha256:a4451ed5c83109d1a36d262b9d6cca1df4f65998fa2933e77b30ffeec2a1688e +size 12980 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png index ec64e498e6..6b4a39b0f8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_null_RoomSummaryRowLight_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3d6f98e42089d2b8b1992a3aeb8c1c91847c3975d3759c00dc75bc85fb0ba5d -size 12195 +oid sha256:496059d16235c52562acf31c209d917b3eff6142324e07476e01a732d9aaf816 +size 12661 From fb9108bfe870b193d63fa7d3ba61b204cb5444c2 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 18 Sep 2023 15:05:43 +0100 Subject: [PATCH 10/12] Fix typo, remove unnecessary filter of all rooms in the datasource. --- .../impl/datasource/RoomListDataSource.kt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index ac0b3ca568..6a9c152af6 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -28,7 +28,6 @@ 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.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.collections.immutable.ImmutableList @@ -57,7 +56,7 @@ class RoomListDataSource @Inject constructor( private val appScope: CoroutineScope, ) { init { - observerNotificationSettings() + observeNotificationSettings() } private val _filter = MutableStateFlow("") @@ -103,7 +102,7 @@ class RoomListDataSource @Inject constructor( val filteredRooms: StateFlow> = _filteredRooms @OptIn(FlowPreview::class) - private fun observerNotificationSettings() { + private fun observeNotificationSettings() { notificationSettingsService.notificationSettingsChangeFlow .debounce(0.5.seconds) .onEach { @@ -140,16 +139,10 @@ class RoomListDataSource @Inject constructor( } } - private fun buildAndCacheItem(roomSummaries: List, index: Int, ): RoomListRoomSummary? { + private fun buildAndCacheItem(roomSummaries: List, index: Int): RoomListRoomSummary? { val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) { is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) is RoomSummary.Filled -> { - // Only show a decoration if the mode is not ALL_MESSAGES - val notificationMode = if (roomSummary.details.notificationMode == RoomNotificationMode.ALL_MESSAGES) { - null - } else { - roomSummary.details.notificationMode - } val avatarData = AvatarData( id = roomSummary.identifier(), name = roomSummary.details.name, @@ -167,7 +160,7 @@ class RoomListDataSource @Inject constructor( roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect) }.orEmpty(), avatarData = avatarData, - notificationMode = notificationMode + notificationMode = roomSummary.details.notificationMode, ) } null -> null From cebe099c7d6d7f3845e620b8b02e663b0a4a9c75 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 18 Sep 2023 16:03:30 +0100 Subject: [PATCH 11/12] Add test. --- .../roomlist/impl/RoomListPresenterTests.kt | 35 +++++++++++++++++-- .../matrix/test/room/RoomSummaryFixture.kt | 5 +++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index a9327f64df..9b10a55461 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.AN_AVATAR_URL @@ -47,6 +48,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -61,6 +63,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.milliseconds class RoomListPresenterTests { @@ -153,7 +156,7 @@ class RoomListPresenterTests { roomListService = roomListService ) val scope = CoroutineScope(coroutineContext + SupervisorJob()) - val presenter = createRoomListPresenter(matrixClient, coroutineScope = scope) + val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -328,6 +331,34 @@ class RoomListPresenterTests { } } + @Test + fun `present - change in notification settings updates the summary for decorations`() = runTest { + val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + val notificationSettingsService = FakeNotificationSettingsService() + val roomListService = FakeRoomListService() + roomListService.postAllRooms(listOf(aRoomSummaryFilled(notificationMode = userDefinedMode))) + val matrixClient = FakeMatrixClient( + roomListService = roomListService, + notificationSettingsService = notificationSettingsService + ) + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val presenter = createRoomListPresenter(client = matrixClient , coroutineScope = scope) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode) + + val updatedState = consumeItemsUntilPredicate { state -> + state.roomList.any { it.id == A_ROOM_ID.value && it.notificationMode == userDefinedMode } + }.last() + + val room = updatedState.roomList.find { it.id == A_ROOM_ID.value } + Truth.assertThat(room?.notificationMode).isEqualTo(userDefinedMode) + cancelAndIgnoreRemainingEvents() + scope.cancel() + } + } + private fun TestScope.createRoomListPresenter( client: MatrixClient = FakeMatrixClient(), sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(), @@ -339,7 +370,7 @@ class RoomListPresenterTests { givenFormat(A_FORMATTED_DATE) }, roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), - coroutineScope: CoroutineScope = this + coroutineScope: CoroutineScope = this, ) = RoomListPresenter( client = client, sessionVerificationService = sessionVerificationService, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 12b0325da5..d3b4dbc577 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.message.RoomMessage @@ -48,6 +49,7 @@ fun aRoomSummaryFilled( lastMessage: RoomMessage? = aRoomMessage(), lastMessageTimestamp: Long? = null, unreadNotificationCount: Int = 2, + notificationMode: RoomNotificationMode? = null, ) = RoomSummary.Filled( aRoomSummaryDetail( roomId = roomId, @@ -57,6 +59,7 @@ fun aRoomSummaryFilled( lastMessage = lastMessage, lastMessageTimestamp = lastMessageTimestamp, unreadNotificationCount = unreadNotificationCount, + notificationMode = notificationMode, ) ) @@ -68,6 +71,7 @@ fun aRoomSummaryDetail( lastMessage: RoomMessage? = aRoomMessage(), lastMessageTimestamp: Long? = null, unreadNotificationCount: Int = 2, + notificationMode: RoomNotificationMode? = null, ) = RoomSummaryDetails( roomId = roomId, name = name, @@ -76,6 +80,7 @@ fun aRoomSummaryDetail( lastMessage = lastMessage, lastMessageTimestamp = lastMessageTimestamp, unreadNotificationCount = unreadNotificationCount, + notificationMode = notificationMode ) fun aRoomMessage( From 2919555764abf156b674e6d6a6567756ea1a97bd Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 18 Sep 2023 16:24:54 +0100 Subject: [PATCH 12/12] Fix unused import. --- .../android/features/roomlist/impl/RoomListPresenterTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 9b10a55461..db2f7027c4 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -63,7 +63,6 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -import kotlin.time.Duration.Companion.milliseconds class RoomListPresenterTests {