Merge pull request #2354 from element-hq/feature/bma/markUnread
Mark room as unread
This commit is contained in:
1
changelog.d/2261.feature
Normal file
1
changelog.d/2261.feature
Normal file
@@ -0,0 +1 @@
|
||||
Manually mark a room as unread
|
||||
@@ -155,6 +155,14 @@ class MessagesPresenter @AssistedInject constructor(
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Mark the room as read on entering but don't send read receipts
|
||||
// as those will be handled by the timeline.
|
||||
withContext(dispatchers.io) {
|
||||
room.markAsRead(null)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(syncUpdateFlow.value) {
|
||||
withContext(dispatchers.io) {
|
||||
canJoinCall = room.canUserJoinCall(room.sessionId).getOrDefault(false)
|
||||
|
||||
@@ -96,7 +96,9 @@ import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -129,6 +131,21 @@ class MessagesPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - check that the room is marked as read`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
assertThat(room.markAsReadCalls).isEmpty()
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
runCurrent()
|
||||
assertThat(room.markAsReadCalls).isEqualTo(listOf(null))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
|
||||
@@ -52,6 +52,7 @@ dependencies {
|
||||
implementation(projects.libraries.eventformatter.api)
|
||||
implementation(projects.libraries.indicator.api)
|
||||
implementation(projects.libraries.deeplink)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.features.invitelist.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.leaveroom.api)
|
||||
@@ -59,6 +60,8 @@ dependencies {
|
||||
api(projects.features.roomlist.api)
|
||||
ksp(libs.showkase.processor)
|
||||
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
@@ -71,6 +74,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.eventformatter.test)
|
||||
testImplementation(projects.libraries.indicator.impl)
|
||||
testImplementation(projects.libraries.permissions.noop)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.features.invitelist.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
|
||||
@@ -41,7 +41,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun RoomListContextMenu(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
eventSink: (RoomListEvents) -> Unit,
|
||||
eventSink: (RoomListEvents.RoomListBottomSheetEvents) -> Unit,
|
||||
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
@@ -49,9 +49,17 @@ fun RoomListContextMenu(
|
||||
) {
|
||||
RoomListModalBottomSheetContent(
|
||||
contextMenu = contextMenu,
|
||||
onRoomMarkReadClicked = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
eventSink(RoomListEvents.MarkAsRead(contextMenu.roomId))
|
||||
},
|
||||
onRoomMarkUnreadClicked = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
eventSink(RoomListEvents.MarkAsUnread(contextMenu.roomId))
|
||||
},
|
||||
onRoomSettingsClicked = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
onRoomSettingsClicked(it)
|
||||
onRoomSettingsClicked(contextMenu.roomId)
|
||||
},
|
||||
onLeaveRoomClicked = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
@@ -64,8 +72,10 @@ fun RoomListContextMenu(
|
||||
@Composable
|
||||
private fun RoomListModalBottomSheetContent(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
||||
onLeaveRoomClicked: (roomId: RoomId) -> Unit,
|
||||
onRoomMarkReadClicked: () -> Unit,
|
||||
onRoomMarkUnreadClicked: () -> Unit,
|
||||
onRoomSettingsClicked: () -> Unit,
|
||||
onLeaveRoomClicked: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -78,6 +88,38 @@ private fun RoomListModalBottomSheetContent(
|
||||
)
|
||||
}
|
||||
)
|
||||
if (contextMenu.markAsUnreadFeatureFlagEnabled) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = if (contextMenu.hasNewContent) {
|
||||
R.string.screen_roomlist_mark_as_read
|
||||
} else {
|
||||
R.string.screen_roomlist_mark_as_unread
|
||||
}
|
||||
),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
if (contextMenu.hasNewContent) {
|
||||
onRoomMarkReadClicked()
|
||||
} else {
|
||||
onRoomMarkUnreadClicked()
|
||||
}
|
||||
},
|
||||
/* TODO Design
|
||||
leadingContent = ListItemContent.Icon(
|
||||
iconSource = IconSource.Vector(
|
||||
CompoundIcons.Settings,
|
||||
contentDescription = stringResource(id = CommonStrings.common_settings)
|
||||
)
|
||||
),
|
||||
*/
|
||||
style = ListItemStyle.Primary,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
@@ -85,7 +127,7 @@ private fun RoomListModalBottomSheetContent(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable { onRoomSettingsClicked(contextMenu.roomId) },
|
||||
modifier = Modifier.clickable { onRoomSettingsClicked() },
|
||||
leadingContent = ListItemContent.Icon(
|
||||
iconSource = IconSource.Vector(
|
||||
CompoundIcons.Settings,
|
||||
@@ -96,14 +138,16 @@ private fun RoomListModalBottomSheetContent(
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
val leaveText = stringResource(id = if (contextMenu.isDm) {
|
||||
CommonStrings.action_leave_conversation
|
||||
} else {
|
||||
CommonStrings.action_leave_room
|
||||
})
|
||||
val leaveText = stringResource(
|
||||
id = if (contextMenu.isDm) {
|
||||
CommonStrings.action_leave_conversation
|
||||
} else {
|
||||
CommonStrings.action_leave_room
|
||||
}
|
||||
)
|
||||
Text(text = leaveText)
|
||||
},
|
||||
modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) },
|
||||
modifier = Modifier.clickable { onLeaveRoomClicked() },
|
||||
leadingContent = ListItemContent.Icon(
|
||||
iconSource = IconSource.Vector(
|
||||
CompoundIcons.Leave,
|
||||
@@ -122,11 +166,9 @@ private fun RoomListModalBottomSheetContent(
|
||||
@Composable
|
||||
internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
||||
RoomListModalBottomSheetContent(
|
||||
contextMenu = RoomListState.ContextMenu.Shown(
|
||||
roomId = RoomId(value = "!aRoom:aDomain"),
|
||||
roomName = "aRoom",
|
||||
isDm = false,
|
||||
),
|
||||
contextMenu = aContextMenuShown(hasNewContent = true),
|
||||
onRoomMarkReadClicked = {},
|
||||
onRoomMarkUnreadClicked = {},
|
||||
onRoomSettingsClicked = {},
|
||||
onLeaveRoomClicked = {}
|
||||
)
|
||||
@@ -136,11 +178,9 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
||||
@Composable
|
||||
internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
|
||||
RoomListModalBottomSheetContent(
|
||||
contextMenu = RoomListState.ContextMenu.Shown(
|
||||
roomId = RoomId(value = "!aRoom:aDomain"),
|
||||
roomName = "aRoom",
|
||||
isDm = true,
|
||||
),
|
||||
contextMenu = aContextMenuShown(isDm = true),
|
||||
onRoomMarkReadClicked = {},
|
||||
onRoomMarkUnreadClicked = {},
|
||||
onRoomSettingsClicked = {},
|
||||
onLeaveRoomClicked = {}
|
||||
)
|
||||
|
||||
@@ -26,6 +26,10 @@ sealed interface RoomListEvents {
|
||||
data object DismissRecoveryKeyPrompt : RoomListEvents
|
||||
data object ToggleSearchResults : RoomListEvents
|
||||
data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
data object HideContextMenu : RoomListEvents
|
||||
data class LeaveRoom(val roomId: RoomId) : RoomListEvents
|
||||
|
||||
sealed interface RoomListBottomSheetEvents : RoomListEvents
|
||||
data object HideContextMenu : RoomListBottomSheetEvents
|
||||
data class LeaveRoom(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
data class MarkAsRead(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
data class MarkAsUnread(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
@@ -44,10 +46,12 @@ import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -65,9 +69,11 @@ class RoomListPresenter @Inject constructor(
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : Presenter<RoomListState> {
|
||||
@Composable
|
||||
override fun present(): RoomListState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val leaveRoomState = leaveRoomPresenter.present()
|
||||
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
|
||||
mutableStateOf(null)
|
||||
@@ -105,6 +111,9 @@ class RoomListPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val markAsUnreadFeatureFlagEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MarkAsUnread)
|
||||
.collectAsState(initial = null)
|
||||
|
||||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
|
||||
@@ -129,10 +138,23 @@ class RoomListPresenter @Inject constructor(
|
||||
roomId = event.roomListRoomSummary.roomId,
|
||||
roomName = event.roomListRoomSummary.name,
|
||||
isDm = event.roomListRoomSummary.isDm,
|
||||
markAsUnreadFeatureFlagEnabled = markAsUnreadFeatureFlagEnabled == true,
|
||||
hasNewContent = event.roomListRoomSummary.hasNewContent
|
||||
)
|
||||
}
|
||||
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||
is RoomListEvents.MarkAsRead -> coroutineScope.launch {
|
||||
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
|
||||
ReceiptType.READ
|
||||
} else {
|
||||
ReceiptType.READ_PRIVATE
|
||||
}
|
||||
client.getRoom(event.roomId)?.markAsRead(receiptType)
|
||||
}
|
||||
is RoomListEvents.MarkAsUnread -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.markAsUnread()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ data class RoomListState(
|
||||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val isDm: Boolean,
|
||||
val markAsUnreadFeatureFlagEnabled: Boolean,
|
||||
val hasNewContent: Boolean,
|
||||
) : ContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
||||
aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()),
|
||||
aRoomListState().copy(displaySearchResults = true),
|
||||
aRoomListState().copy(
|
||||
contextMenu = RoomListState.ContextMenu.Shown(
|
||||
roomId = RoomId("!aRoom:aDomain"),
|
||||
roomName = "A nice room name",
|
||||
isDm = false,
|
||||
)
|
||||
),
|
||||
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||
@@ -103,3 +97,15 @@ internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aContextMenuShown(
|
||||
roomName: String = "aRoom",
|
||||
isDm: Boolean = false,
|
||||
hasNewContent: Boolean = false,
|
||||
) = RoomListState.ContextMenu.Shown(
|
||||
roomId = RoomId("!aRoom:aDomain"),
|
||||
roomName = roomName,
|
||||
isDm = isDm,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = hasNewContent,
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||
numberOfUnreadMessages = 0,
|
||||
numberOfUnreadMentions = 0,
|
||||
numberOfUnreadNotifications = 0,
|
||||
isMarkedUnread = false,
|
||||
userDefinedNotificationMode = null,
|
||||
hasRoomCall = false,
|
||||
isDm = false,
|
||||
@@ -73,6 +74,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||
numberOfUnreadMessages = roomSummary.details.numUnreadMessages,
|
||||
numberOfUnreadMentions = roomSummary.details.numUnreadMentions,
|
||||
numberOfUnreadNotifications = roomSummary.details.numUnreadNotifications,
|
||||
isMarkedUnread = roomSummary.details.isMarkedUnread,
|
||||
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
|
||||
lastMessage = roomSummary.details.lastMessage?.let { message ->
|
||||
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
||||
|
||||
@@ -29,6 +29,7 @@ data class RoomListRoomSummary(
|
||||
val numberOfUnreadMessages: Int,
|
||||
val numberOfUnreadMentions: Int,
|
||||
val numberOfUnreadNotifications: Int,
|
||||
val isMarkedUnread: Boolean,
|
||||
val timestamp: String?,
|
||||
val lastMessage: CharSequence?,
|
||||
val avatarData: AvatarData,
|
||||
@@ -38,9 +39,11 @@ data class RoomListRoomSummary(
|
||||
val isDm: Boolean,
|
||||
) {
|
||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0)
|
||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||
isMarkedUnread
|
||||
|
||||
val hasNewContent = numberOfUnreadMessages > 0 ||
|
||||
numberOfUnreadMentions > 0 ||
|
||||
numberOfUnreadNotifications > 0
|
||||
numberOfUnreadNotifications > 0 ||
|
||||
isMarkedUnread
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ internal fun aRoomListRoomSummary(
|
||||
numberOfUnreadMessages: Int = 0,
|
||||
numberOfUnreadMentions: Int = 0,
|
||||
numberOfUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
lastMessage: String? = "Last message",
|
||||
timestamp: String? = lastMessage?.let { "88:88" },
|
||||
isPlaceholder: Boolean = false,
|
||||
@@ -103,6 +104,7 @@ internal fun aRoomListRoomSummary(
|
||||
numberOfUnreadMessages = numberOfUnreadMessages,
|
||||
numberOfUnreadMentions = numberOfUnreadMentions,
|
||||
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
timestamp = timestamp,
|
||||
lastMessage = lastMessage,
|
||||
avatarData = avatarData,
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RoomListContextMenuTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on Mark as read generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(hasNewContent = true)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_roomlist_mark_as_read)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideContextMenu,
|
||||
RoomListEvents.MarkAsRead(contextMenu.roomId),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Mark as unread generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(hasNewContent = false)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_roomlist_mark_as_unread)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideContextMenu,
|
||||
RoomListEvents.MarkAsUnread(contextMenu.roomId),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Leave dm generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(isDm = true)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_leave_conversation)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideContextMenu,
|
||||
RoomListEvents.LeaveRoom(contextMenu.roomId),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Leave room generates expected Events`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(isDm = false)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = EnsureNeverCalledWithParam(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_leave_room)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.HideContextMenu,
|
||||
RoomListEvents.LeaveRoom(contextMenu.roomId),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Settings invokes the expected callback and generates expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown()
|
||||
val callback = EnsureCalledOnceWithParam(contextMenu.roomId)
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = callback,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.common_settings)
|
||||
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
|
||||
callback.assertSuccess()
|
||||
}
|
||||
}
|
||||
@@ -25,28 +25,30 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
||||
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
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.timeline.ReceiptType
|
||||
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
|
||||
@@ -59,6 +61,7 @@ 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.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
@@ -175,12 +178,23 @@ class RoomListPresenterTests {
|
||||
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
||||
assertThat(initialItems.size).isEqualTo(16)
|
||||
assertThat(initialItems.all { it.isPlaceholder }).isTrue()
|
||||
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
||||
roomListService.postAllRooms(
|
||||
listOf(
|
||||
aRoomSummaryFilled(
|
||||
numUnreadMentions = 1,
|
||||
numUnreadMessages = 2,
|
||||
)
|
||||
)
|
||||
)
|
||||
val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 1 }.last()
|
||||
val withRoomStateItems = withRoomState.roomList.dataOrNull().orEmpty()
|
||||
assertThat(withRoomStateItems.size).isEqualTo(1)
|
||||
assertThat(withRoomStateItems.first())
|
||||
.isEqualTo(aRoomListRoomSummary)
|
||||
assertThat(withRoomStateItems.first()).isEqualTo(
|
||||
createRoomListRoomSummary(
|
||||
numberOfUnreadMentions = 1,
|
||||
numberOfUnreadMessages = 2,
|
||||
)
|
||||
)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -196,7 +210,14 @@ class RoomListPresenterTests {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
||||
roomListService.postAllRooms(
|
||||
listOf(
|
||||
aRoomSummaryFilled(
|
||||
numUnreadMentions = 1,
|
||||
numUnreadMessages = 2,
|
||||
)
|
||||
)
|
||||
)
|
||||
skipItems(3)
|
||||
val loadedState = awaitItem()
|
||||
// Test filtering with result
|
||||
@@ -207,8 +228,12 @@ class RoomListPresenterTests {
|
||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
||||
assertThat(withFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3))
|
||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
||||
assertThat(withFilteredRoomState.filteredRoomList.first())
|
||||
.isEqualTo(aRoomListRoomSummary)
|
||||
assertThat(withFilteredRoomState.filteredRoomList.first()).isEqualTo(
|
||||
createRoomListRoomSummary(
|
||||
numberOfUnreadMentions = 1,
|
||||
numberOfUnreadMessages = 2,
|
||||
)
|
||||
)
|
||||
// Test filtering without result
|
||||
withFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada"))
|
||||
skipItems(1)
|
||||
@@ -322,12 +347,19 @@ class RoomListPresenterTests {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
val summary = aRoomListRoomSummary
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
||||
val shownState = awaitItem()
|
||||
assertThat(shownState.contextMenu)
|
||||
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
|
||||
assertThat(shownState.contextMenu).isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
@@ -342,12 +374,19 @@ class RoomListPresenterTests {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
val summary = aRoomListRoomSummary
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
||||
val shownState = awaitItem()
|
||||
assertThat(shownState.contextMenu)
|
||||
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
|
||||
assertThat(shownState.contextMenu).isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
)
|
||||
shownState.eventSink(RoomListEvents.HideContextMenu)
|
||||
|
||||
val hiddenState = awaitItem()
|
||||
@@ -430,6 +469,41 @@ class RoomListPresenterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - check that the room is marked as read with correct RR and as unread`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val sessionPreferencesStore = InMemorySessionPreferencesStore()
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(room.markAsReadCalls).isEmpty()
|
||||
assertThat(room.markAsUnreadReadCallCount).isEqualTo(0)
|
||||
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
|
||||
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ))
|
||||
assertThat(room.markAsUnreadReadCallCount).isEqualTo(0)
|
||||
initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID))
|
||||
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ))
|
||||
assertThat(room.markAsUnreadReadCallCount).isEqualTo(1)
|
||||
// Test again with private read receipts
|
||||
sessionPreferencesStore.setSendPublicReadReceipts(false)
|
||||
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
|
||||
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ, ReceiptType.READ_PRIVATE))
|
||||
assertThat(room.markAsUnreadReadCallCount).isEqualTo(1)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRoomListPresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||
@@ -442,6 +516,7 @@ class RoomListPresenterTests {
|
||||
},
|
||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
coroutineScope: CoroutineScope,
|
||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = client,
|
||||
@@ -472,23 +547,6 @@ class RoomListPresenterTests {
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||
),
|
||||
migrationScreenPresenter = migrationScreenPresenter,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
)
|
||||
}
|
||||
|
||||
private const val A_FORMATTED_DATE = "formatted_date"
|
||||
|
||||
private val aRoomListRoomSummary = RoomListRoomSummary(
|
||||
id = A_ROOM_ID.value,
|
||||
roomId = A_ROOM_ID,
|
||||
name = A_ROOM_NAME,
|
||||
numberOfUnreadMentions = 1,
|
||||
numberOfUnreadMessages = 2,
|
||||
numberOfUnreadNotifications = 0,
|
||||
timestamp = A_FORMATTED_DATE,
|
||||
lastMessage = "",
|
||||
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
|
||||
isPlaceholder = false,
|
||||
userDefinedNotificationMode = null,
|
||||
hasRoomCall = false,
|
||||
isDm = false,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.model
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE
|
||||
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.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import org.junit.Test
|
||||
|
||||
class RoomListRoomSummaryTest {
|
||||
@Test
|
||||
fun `test default value`() {
|
||||
val sut = createRoomListRoomSummary(
|
||||
isMarkedUnread = false,
|
||||
)
|
||||
assertThat(sut.isHighlighted).isFalse()
|
||||
assertThat(sut.hasNewContent).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test muted room`() {
|
||||
val sut = createRoomListRoomSummary(
|
||||
userDefinedNotificationMode = RoomNotificationMode.MUTE,
|
||||
)
|
||||
assertThat(sut.isHighlighted).isFalse()
|
||||
assertThat(sut.hasNewContent).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test muted room isMarkedUnread set to true`() {
|
||||
val sut = createRoomListRoomSummary(
|
||||
isMarkedUnread = true,
|
||||
userDefinedNotificationMode = RoomNotificationMode.MUTE,
|
||||
)
|
||||
assertThat(sut.isHighlighted).isTrue()
|
||||
assertThat(sut.hasNewContent).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test muted room with unread message`() {
|
||||
val sut = createRoomListRoomSummary(
|
||||
numberOfUnreadNotifications = 1,
|
||||
userDefinedNotificationMode = RoomNotificationMode.MUTE,
|
||||
)
|
||||
assertThat(sut.isHighlighted).isFalse()
|
||||
assertThat(sut.hasNewContent).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isMarkedUnread set to true`() {
|
||||
val sut = createRoomListRoomSummary(
|
||||
isMarkedUnread = true,
|
||||
)
|
||||
assertThat(sut.isHighlighted).isTrue()
|
||||
assertThat(sut.hasNewContent).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createRoomListRoomSummary(
|
||||
numberOfUnreadMentions: Int = 0,
|
||||
numberOfUnreadMessages: Int = 0,
|
||||
numberOfUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||
) = RoomListRoomSummary(
|
||||
id = A_ROOM_ID.value,
|
||||
roomId = A_ROOM_ID,
|
||||
name = A_ROOM_NAME,
|
||||
numberOfUnreadMentions = numberOfUnreadMentions,
|
||||
numberOfUnreadMessages = numberOfUnreadMessages,
|
||||
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
timestamp = A_FORMATTED_DATE,
|
||||
lastMessage = "",
|
||||
avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem),
|
||||
isPlaceholder = false,
|
||||
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||
hasRoomCall = false,
|
||||
isDm = false,
|
||||
)
|
||||
@@ -18,6 +18,8 @@ package io.element.android.libraries.dateformatter.test
|
||||
|
||||
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||
|
||||
const val A_FORMATTED_DATE = "formatted_date"
|
||||
|
||||
class FakeLastMessageTimestampFormatter : LastMessageTimestampFormatter {
|
||||
private var format = ""
|
||||
fun givenFormat(format: String) {
|
||||
|
||||
@@ -75,4 +75,11 @@ enum class FeatureFlags(
|
||||
defaultValue = true,
|
||||
isFinished = false,
|
||||
),
|
||||
MarkAsUnread(
|
||||
key = "feature.markAsUnread",
|
||||
title = "Mark as unread",
|
||||
description = "Allow user to mark a room as unread",
|
||||
defaultValue = true,
|
||||
isFinished = false,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
||||
FeatureFlags.PinUnlock -> true
|
||||
FeatureFlags.Mentions -> true
|
||||
FeatureFlags.SecureStorage -> true
|
||||
FeatureFlags.MarkAsUnread -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -150,6 +151,17 @@ interface MatrixRoom : Closeable {
|
||||
|
||||
suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit>
|
||||
|
||||
/**
|
||||
* Reverts a previously set unread flag, and eventually send a Read Receipt.
|
||||
* @param receiptType The type of receipt to send. If null, no Read Receipt will be sent.
|
||||
*/
|
||||
suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit>
|
||||
|
||||
/**
|
||||
* Sets a flag on the room to indicate that the user has explicitly marked it as unread.
|
||||
*/
|
||||
suspend fun markAsUnread(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Share a location message in the room.
|
||||
*
|
||||
|
||||
@@ -43,6 +43,7 @@ data class RoomSummaryDetails(
|
||||
val numUnreadMessages: Int,
|
||||
val numUnreadMentions: Int,
|
||||
val numUnreadNotifications: Int,
|
||||
val isMarkedUnread: Boolean,
|
||||
val inviter: RoomMember?,
|
||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||
val hasRoomCall: Boolean,
|
||||
|
||||
@@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
||||
@@ -51,6 +52,7 @@ import io.element.android.libraries.matrix.impl.poll.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.location.toInner
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
||||
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
|
||||
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
|
||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
||||
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
|
||||
@@ -423,6 +425,22 @@ class RustMatrixRoom(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
if (receiptType != null) {
|
||||
innerRoom.markAsReadAndSendReadReceipt(receiptType.toRustReceiptType())
|
||||
} else {
|
||||
innerRoom.markAsRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markAsUnread(): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.markAsUnread()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendLocation(
|
||||
body: String,
|
||||
geoUri: String,
|
||||
|
||||
@@ -38,6 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
||||
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
|
||||
numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
|
||||
numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
|
||||
isMarkedUnread = roomInfo.isMarkedUnread,
|
||||
lastMessage = latestRoomMessage,
|
||||
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
|
||||
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
|
||||
|
||||
@@ -41,6 +41,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
@@ -374,6 +375,20 @@ class FakeMatrixRoom(
|
||||
return reportContentResult
|
||||
}
|
||||
|
||||
val markAsReadCalls = mutableListOf<ReceiptType?>()
|
||||
override suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit> {
|
||||
markAsReadCalls.add(receiptType)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
var markAsUnreadReadCallCount = 0
|
||||
private set
|
||||
|
||||
override suspend fun markAsUnread(): Result<Unit> {
|
||||
markAsUnreadReadCallCount++
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun sendLocation(
|
||||
body: String,
|
||||
geoUri: String,
|
||||
|
||||
@@ -37,8 +37,8 @@ fun aRoomSummaryFilled(
|
||||
isDirect: Boolean = false,
|
||||
avatarUrl: String? = null,
|
||||
lastMessage: RoomMessage? = aRoomMessage(),
|
||||
numUnreadMentions: Int = 1,
|
||||
numUnreadMessages: Int = 2,
|
||||
numUnreadMentions: Int = 0,
|
||||
numUnreadMessages: Int = 0,
|
||||
notificationMode: RoomNotificationMode? = null,
|
||||
) = RoomSummary.Filled(
|
||||
aRoomSummaryDetails(
|
||||
@@ -62,6 +62,7 @@ fun aRoomSummaryDetails(
|
||||
numUnreadMentions: Int = 0,
|
||||
numUnreadMessages: Int = 0,
|
||||
numUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
notificationMode: RoomNotificationMode? = null,
|
||||
inviter: RoomMember? = null,
|
||||
canonicalAlias: String? = null,
|
||||
@@ -76,6 +77,7 @@ fun aRoomSummaryDetails(
|
||||
numUnreadMentions = numUnreadMentions,
|
||||
numUnreadMessages = numUnreadMessages,
|
||||
numUnreadNotifications = numUnreadNotifications,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
userDefinedNotificationMode = notificationMode,
|
||||
inviter = inviter,
|
||||
canonicalAlias = canonicalAlias,
|
||||
|
||||
@@ -116,6 +116,7 @@ fun aRoomSummaryDetails(
|
||||
numUnreadMentions: Int = 0,
|
||||
numUnreadMessages: Int = 0,
|
||||
numUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
) = RoomSummaryDetails(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
@@ -130,4 +131,5 @@ fun aRoomSummaryDetails(
|
||||
numUnreadMentions = numUnreadMentions,
|
||||
numUnreadMessages = numUnreadMessages,
|
||||
numUnreadNotifications = numUnreadNotifications,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
)
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies {
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.dateformatter.impl)
|
||||
implementation(projects.libraries.eventformatter.impl)
|
||||
implementation(projects.libraries.preferences.impl)
|
||||
implementation(projects.libraries.indicator.impl)
|
||||
implementation(projects.features.invitelist.impl)
|
||||
implementation(projects.features.roomlist.impl)
|
||||
|
||||
@@ -45,6 +45,7 @@ import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStore
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -108,7 +109,12 @@ class RoomListScreen(
|
||||
migrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = matrixClient,
|
||||
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
||||
)
|
||||
),
|
||||
sessionPreferencesStore = DefaultSessionPreferencesStore(
|
||||
context = context,
|
||||
sessionId = matrixClient.sessionId,
|
||||
sessionCoroutineScope = Singleton.appScope
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user