From 882d5577a7d36649cf5747a0b867977109485d97 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 May 2026 18:06:11 +0200 Subject: [PATCH] Add "Mark as read", "Mark as unread" in room settings. #6398 --- features/roomdetails/impl/build.gradle.kts | 1 + .../roomdetails/impl/RoomDetailsEvent.kt | 2 + .../roomdetails/impl/RoomDetailsPresenter.kt | 38 +++++++++++++++++ .../roomdetails/impl/RoomDetailsState.kt | 1 + .../impl/RoomDetailsStateProvider.kt | 4 +- .../roomdetails/impl/RoomDetailsView.kt | 41 +++++++++++++++++++ .../impl/src/main/res/values/localazy.xml | 2 + tools/localazy/config.json | 1 + 8 files changed, 89 insertions(+), 1 deletion(-) diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 2765a95a1e..23b23a3e62 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.libraries.featureflag.api) implementation(projects.libraries.permissions.api) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.push.api) implementation(projects.libraries.testtags) api(projects.features.roomdetails.api) api(projects.libraries.usersearch.api) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index de801e8aae..a82766175d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -14,4 +14,6 @@ sealed interface RoomDetailsEvent { data object UnmuteNotification : RoomDetailsEvent data class CopyToClipboard(val text: String) : RoomDetailsEvent data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent + data object MarkAsRead : RoomDetailsEvent + data object MarkAsUnread : RoomDetailsEvent } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 167cc2ced5..9f61c12d64 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -44,13 +44,17 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -67,6 +71,8 @@ class RoomDetailsPresenter( private val analyticsService: AnalyticsService, private val clipboardHelper: ClipboardHelper, private val appPreferencesStore: AppPreferencesStore, + private val sessionPreferencesStore: SessionPreferencesStore, + private val notificationCleaner: NotificationCleaner, ) : Presenter { @Composable override fun present(): RoomDetailsState { @@ -79,6 +85,14 @@ class RoomDetailsPresenter( val roomTopic by remember { derivedStateOf { roomInfo.topic } } val isFavorite by remember { derivedStateOf { roomInfo.isFavorite } } val joinRule by remember { derivedStateOf { roomInfo.joinRule } } + val hasNewContent by remember { + derivedStateOf { + roomInfo.numUnreadMessages > 0 || + roomInfo.numUnreadMentions > 0 || + roomInfo.numUnreadNotifications > 0 || + roomInfo.isMarkedUnread + } + } val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } } @@ -145,6 +159,8 @@ class RoomDetailsPresenter( clipboardHelper.copyPlainText(event.text) snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard)) } + is RoomDetailsEvent.MarkAsRead -> scope.markAsRead() + is RoomDetailsEvent.MarkAsUnread -> scope.markAsUnread() } } @@ -188,6 +204,7 @@ class RoomDetailsPresenter( showDebugInfo = isDeveloperModeEnabled, roomVersion = roomInfo.roomVersion, roomHistoryVisibility = roomInfo.historyVisibility, + hasNewContent = hasNewContent, eventSink = ::handleEvent, ) } @@ -241,4 +258,25 @@ class RoomDetailsPresenter( analyticsService.captureInteraction(Interaction.Name.MobileRoomFavouriteToggle) } } + + private fun CoroutineScope.markAsRead() = launch { + notificationCleaner.clearMessagesForRoom(client.sessionId, room.roomId) + room.setUnreadFlag(isUnread = false) + val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) { + ReceiptType.READ + } else { + ReceiptType.READ_PRIVATE + } + room.markAsRead(receiptType) + .onSuccess { + analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle) + } + } + + private fun CoroutineScope.markAsUnread() = launch { + room.setUnreadFlag(isUnread = true) + .onSuccess { + analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle) + } + } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 90a912e86f..bd3b90d416 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -52,6 +52,7 @@ data class RoomDetailsState( val showDebugInfo: Boolean, val roomVersion: String?, val roomHistoryVisibility: RoomHistoryVisibility, + val hasNewContent: Boolean, val eventSink: (RoomDetailsEvent) -> Unit ) { val roomBadges = buildList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index b569527d9c..7182d2ec04 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -34,7 +34,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider override val values: Sequence get() = sequenceOf( aRoomDetailsState(displayAdminSettings = true), - aRoomDetailsState(roomTopic = RoomTopicState.Hidden, showDebugInfo = true), + aRoomDetailsState(roomTopic = RoomTopicState.Hidden, showDebugInfo = true, hasNewContent = true), aRoomDetailsState(roomTopic = RoomTopicState.CanAddTopic), aRoomDetailsState(isEncrypted = false), aRoomDetailsState(roomAlias = null), @@ -123,6 +123,7 @@ fun aRoomDetailsState( isTombstoned: Boolean = false, showDebugInfo: Boolean = false, roomHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Shared, + hasNewContent: Boolean = false, eventSink: (RoomDetailsEvent) -> Unit = {}, ) = RoomDetailsState( roomId = roomId, @@ -154,6 +155,7 @@ fun aRoomDetailsState( showDebugInfo = showDebugInfo, roomVersion = "12", roomHistoryVisibility = roomHistoryVisibility, + hasNewContent = hasNewContent, eventSink = eventSink, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index cf5265b145..b30c73b9c8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -188,6 +189,46 @@ fun RoomDetailsView( ) } + PreferenceCategory { + if (state.hasNewContent) { + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_roomlist_mark_as_read), + style = MaterialTheme.typography.bodyLarge, + ) + }, + onClick = { + state.eventSink(RoomDetailsEvent.MarkAsRead) + }, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(CompoundIcons.MarkAsRead()) + ), + trailingContent = ListItemContent.Custom { + Box( + modifier = modifier + .size(8.dp) + .clip(CircleShape) + .background(ElementTheme.colors.iconAccentPrimary) + ) + }, + ) + } else { + ListItem( + headlineContent = { + Text( + text = stringResource(id = R.string.screen_roomlist_mark_as_unread), + ) + }, + onClick = { + state.eventSink(RoomDetailsEvent.MarkAsUnread) + }, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector(CompoundIcons.MarkAsUnread()) + ), + ) + } + } PreferenceCategory { if (state.roomNotificationSettings != null) { NotificationItem( diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index d7092af70e..0287b5657b 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -132,6 +132,8 @@ "Roles" "Room details" "Roles & permissions" + "Mark as read" + "Mark as unread" "Add address" "Anyone in authorised spaces can join, but everyone else must request access." "Everyone must request access." diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 6ea495439e..cb9c2ecb0d 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -221,6 +221,7 @@ "screen_notification_settings_mentions_only_disclaimer", "screen_room_change_.*", "screen_room_roles_.*", + "screen_roomlist_mark_as_.*", "screen\\.edit_room_address\\..*", "screen\\.security_and_privacy\\..*" ]