Skip to content

Commit dea808c

Browse files
authored
Merge pull request #6818 from element-hq/feature/bma/markAsUnreadInRoomDetails
Add mark as read / unread in room details
2 parents c3a4d64 + 260ea4a commit dea808c

66 files changed

Lines changed: 351 additions & 102 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ class LoggedInFlowNode(
382382
}
383383
is NavTarget.Room -> {
384384
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
385+
override fun onDone() {
386+
backstack.pop()
387+
}
388+
385389
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) {
386390
lifecycleScope.launch {
387391
attachRoom(roomIdOrAlias = roomId.toRoomIdOrAlias(), serverNames = serverNames, clearBackstack = clearBackStack)

appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class JoinedRoomLoadedFlowNode(
8282
plugins = plugins,
8383
), DependencyInjectionGraphOwner {
8484
interface Callback : Plugin {
85+
fun onDone()
8586
fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean = false)
8687
fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
8788
fun navigateToGlobalNotificationSettings()
@@ -142,6 +143,10 @@ class JoinedRoomLoadedFlowNode(
142143

143144
private fun createRoomDetailsNode(buildContext: BuildContext, initialTarget: RoomDetailsEntryPoint.InitialTarget): Node {
144145
val callback = object : RoomDetailsEntryPoint.Callback {
146+
override fun onDone() {
147+
callback.onDone()
148+
}
149+
145150
override fun navigateToGlobalNotificationSettings() {
146151
callback.navigateToGlobalNotificationSettings()
147152
}

appnav/src/test/kotlin/io/element/android/appnav/room/joined/FakeJoinedRoomLoadedFlowNodeCallback.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
1313
import io.element.android.tests.testutils.lambda.lambdaError
1414

1515
class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback {
16+
override fun onDone() = lambdaError()
1617
override fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean) = lambdaError()
1718
override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
1819
override fun navigateToGlobalNotificationSettings() = lambdaError()

features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
3838
data class Params(val initialElement: InitialTarget) : NodeInputs
3939

4040
interface Callback : Plugin {
41+
fun onDone()
4142
fun navigateToGlobalNotificationSettings()
4243
fun navigateToDeveloperSettings()
4344
fun navigateToRoom(roomId: RoomId, serverNames: List<String>, clearBackStack: Boolean = false)

features/roomdetails/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
implementation(projects.libraries.featureflag.api)
4141
implementation(projects.libraries.permissions.api)
4242
implementation(projects.libraries.preferences.api)
43+
implementation(projects.libraries.push.api)
4344
implementation(projects.libraries.testtags)
4445
api(projects.features.roomdetails.api)
4546
api(projects.libraries.usersearch.api)
@@ -69,6 +70,7 @@ dependencies {
6970
testImplementation(projects.libraries.mediaviewer.test)
7071
testImplementation(projects.libraries.permissions.test)
7172
testImplementation(projects.libraries.preferences.test)
73+
testImplementation(projects.libraries.push.test)
7274
testImplementation(projects.libraries.usersearch.test)
7375
testImplementation(projects.libraries.featureflag.test)
7476
testImplementation(projects.features.call.test)

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ sealed interface RoomDetailsEvent {
1414
data object UnmuteNotification : RoomDetailsEvent
1515
data class CopyToClipboard(val text: String) : RoomDetailsEvent
1616
data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent
17+
data object MarkAsRead : RoomDetailsEvent
18+
data object MarkAsUnread : RoomDetailsEvent
1719
}

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ class RoomDetailsFlowNode(
176176
return when (navTarget) {
177177
NavTarget.RoomDetails -> {
178178
val roomDetailsCallback = object : RoomDetailsNode.Callback {
179+
override fun navigateBack() {
180+
callback.onDone()
181+
}
182+
179183
override fun navigateToRoomMemberList() {
180184
backstack.push(NavTarget.RoomMemberList)
181185
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright (c) 2026 Element Creations Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.roomdetails.impl
9+
10+
interface RoomDetailsNavigator {
11+
fun onDone()
12+
}

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ import io.element.android.libraries.androidutils.R as AndroidUtilsR
4242
class RoomDetailsNode(
4343
@Assisted buildContext: BuildContext,
4444
@Assisted plugins: List<Plugin>,
45-
private val presenter: RoomDetailsPresenter,
45+
presenterFactory: RoomDetailsPresenter.Factory,
4646
private val room: BaseRoom,
4747
private val analyticsService: AnalyticsService,
4848
private val leaveRoomRenderer: LeaveRoomRenderer,
49-
) : Node(buildContext, plugins = plugins) {
49+
) : Node(buildContext, plugins = plugins), RoomDetailsNavigator {
5050
interface Callback : Plugin {
51+
fun navigateBack()
5152
fun navigateToRoomMemberList()
5253
fun navigateToInviteMembers()
5354
fun navigateToRoomDetailsEdit()
@@ -65,6 +66,7 @@ class RoomDetailsNode(
6566
fun navigateToSelectNewOwnersWhenLeaving()
6667
}
6768

69+
private val presenter = presenterFactory.create(this)
6870
private val callback: Callback = callback()
6971

7072
init {
@@ -144,4 +146,8 @@ class RoomDetailsNode(
144146
}
145147
)
146148
}
149+
150+
override fun onDone() {
151+
callback.navigateBack()
152+
}
147153
}

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import androidx.compose.runtime.getValue
1717
import androidx.compose.runtime.produceState
1818
import androidx.compose.runtime.remember
1919
import androidx.compose.runtime.rememberCoroutineScope
20-
import dev.zacsweers.metro.Inject
20+
import dev.zacsweers.metro.Assisted
21+
import dev.zacsweers.metro.AssistedFactory
22+
import dev.zacsweers.metro.AssistedInject
2123
import im.vector.app.features.analytics.plan.Interaction
2224
import io.element.android.features.knockrequests.api.KnockRequestPermissions
2325
import io.element.android.features.knockrequests.api.knockRequestPermissions
@@ -44,19 +46,24 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule
4446
import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions
4547
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
4648
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
49+
import io.element.android.libraries.matrix.api.timeline.ReceiptType
4750
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
4851
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
4952
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
53+
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
54+
import io.element.android.libraries.push.api.notifications.NotificationCleaner
5055
import io.element.android.libraries.ui.strings.CommonStrings
5156
import io.element.android.services.analytics.api.AnalyticsService
5257
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
5358
import kotlinx.coroutines.CoroutineScope
59+
import kotlinx.coroutines.flow.first
5460
import kotlinx.coroutines.flow.launchIn
5561
import kotlinx.coroutines.flow.onEach
5662
import kotlinx.coroutines.launch
5763

58-
@Inject
64+
@AssistedInject
5965
class RoomDetailsPresenter(
66+
@Assisted private val navigator: RoomDetailsNavigator,
6067
private val client: MatrixClient,
6168
private val room: JoinedRoom,
6269
private val notificationSettingsService: NotificationSettingsService,
@@ -67,7 +74,16 @@ class RoomDetailsPresenter(
6774
private val analyticsService: AnalyticsService,
6875
private val clipboardHelper: ClipboardHelper,
6976
private val appPreferencesStore: AppPreferencesStore,
77+
private val sessionPreferencesStore: SessionPreferencesStore,
78+
private val notificationCleaner: NotificationCleaner,
7079
) : Presenter<RoomDetailsState> {
80+
@AssistedFactory
81+
interface Factory {
82+
fun create(
83+
navigator: RoomDetailsNavigator,
84+
): RoomDetailsPresenter
85+
}
86+
7187
@Composable
7288
override fun present(): RoomDetailsState {
7389
val scope = rememberCoroutineScope()
@@ -79,6 +95,14 @@ class RoomDetailsPresenter(
7995
val roomTopic by remember { derivedStateOf { roomInfo.topic } }
8096
val isFavorite by remember { derivedStateOf { roomInfo.isFavorite } }
8197
val joinRule by remember { derivedStateOf { roomInfo.joinRule } }
98+
val hasNewContent by remember {
99+
derivedStateOf {
100+
roomInfo.numUnreadMessages > 0 ||
101+
roomInfo.numUnreadMentions > 0 ||
102+
roomInfo.numUnreadNotifications > 0 ||
103+
roomInfo.isMarkedUnread
104+
}
105+
}
82106

83107
val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } }
84108

@@ -145,6 +169,8 @@ class RoomDetailsPresenter(
145169
clipboardHelper.copyPlainText(event.text)
146170
snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard))
147171
}
172+
is RoomDetailsEvent.MarkAsRead -> scope.markAsRead()
173+
is RoomDetailsEvent.MarkAsUnread -> scope.markAsUnread()
148174
}
149175
}
150176

@@ -188,6 +214,7 @@ class RoomDetailsPresenter(
188214
showDebugInfo = isDeveloperModeEnabled,
189215
roomVersion = roomInfo.roomVersion,
190216
roomHistoryVisibility = roomInfo.historyVisibility,
217+
hasNewContent = hasNewContent,
191218
eventSink = ::handleEvent,
192219
)
193220
}
@@ -241,4 +268,26 @@ class RoomDetailsPresenter(
241268
analyticsService.captureInteraction(Interaction.Name.MobileRoomFavouriteToggle)
242269
}
243270
}
271+
272+
private fun CoroutineScope.markAsRead() = launch {
273+
notificationCleaner.clearMessagesForRoom(client.sessionId, room.roomId)
274+
room.setUnreadFlag(isUnread = false)
275+
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
276+
ReceiptType.READ
277+
} else {
278+
ReceiptType.READ_PRIVATE
279+
}
280+
room.markAsRead(receiptType)
281+
.onSuccess {
282+
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
283+
}
284+
}
285+
286+
private fun CoroutineScope.markAsUnread() = launch {
287+
room.setUnreadFlag(isUnread = true)
288+
.onSuccess {
289+
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
290+
navigator.onDone()
291+
}
292+
}
244293
}

0 commit comments

Comments
 (0)