Merge pull request #2354 from element-hq/feature/bma/markUnread
Mark room as unread
This commit is contained in:
commit
e73a480495
31 changed files with 542 additions and 76 deletions
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)
|
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) {
|
LaunchedEffect(syncUpdateFlow.value) {
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
canJoinCall = room.canUserJoinCall(room.sessionId).getOrDefault(false)
|
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.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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
|
@Test
|
||||||
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
|
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
|
||||||
val room = FakeMatrixRoom().apply {
|
val room = FakeMatrixRoom().apply {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ dependencies {
|
||||||
implementation(projects.libraries.eventformatter.api)
|
implementation(projects.libraries.eventformatter.api)
|
||||||
implementation(projects.libraries.indicator.api)
|
implementation(projects.libraries.indicator.api)
|
||||||
implementation(projects.libraries.deeplink)
|
implementation(projects.libraries.deeplink)
|
||||||
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.features.invitelist.api)
|
implementation(projects.features.invitelist.api)
|
||||||
implementation(projects.features.networkmonitor.api)
|
implementation(projects.features.networkmonitor.api)
|
||||||
implementation(projects.features.leaveroom.api)
|
implementation(projects.features.leaveroom.api)
|
||||||
|
|
@ -59,6 +60,8 @@ dependencies {
|
||||||
api(projects.features.roomlist.api)
|
api(projects.features.roomlist.api)
|
||||||
ksp(libs.showkase.processor)
|
ksp(libs.showkase.processor)
|
||||||
|
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
|
|
@ -71,6 +74,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.eventformatter.test)
|
testImplementation(projects.libraries.eventformatter.test)
|
||||||
testImplementation(projects.libraries.indicator.impl)
|
testImplementation(projects.libraries.indicator.impl)
|
||||||
testImplementation(projects.libraries.permissions.noop)
|
testImplementation(projects.libraries.permissions.noop)
|
||||||
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.features.invitelist.test)
|
testImplementation(projects.features.invitelist.test)
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
testImplementation(projects.features.networkmonitor.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomListContextMenu(
|
fun RoomListContextMenu(
|
||||||
contextMenu: RoomListState.ContextMenu.Shown,
|
contextMenu: RoomListState.ContextMenu.Shown,
|
||||||
eventSink: (RoomListEvents) -> Unit,
|
eventSink: (RoomListEvents.RoomListBottomSheetEvents) -> Unit,
|
||||||
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
||||||
) {
|
) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
|
|
@ -49,9 +49,17 @@ fun RoomListContextMenu(
|
||||||
) {
|
) {
|
||||||
RoomListModalBottomSheetContent(
|
RoomListModalBottomSheetContent(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
|
onRoomMarkReadClicked = {
|
||||||
|
eventSink(RoomListEvents.HideContextMenu)
|
||||||
|
eventSink(RoomListEvents.MarkAsRead(contextMenu.roomId))
|
||||||
|
},
|
||||||
|
onRoomMarkUnreadClicked = {
|
||||||
|
eventSink(RoomListEvents.HideContextMenu)
|
||||||
|
eventSink(RoomListEvents.MarkAsUnread(contextMenu.roomId))
|
||||||
|
},
|
||||||
onRoomSettingsClicked = {
|
onRoomSettingsClicked = {
|
||||||
eventSink(RoomListEvents.HideContextMenu)
|
eventSink(RoomListEvents.HideContextMenu)
|
||||||
onRoomSettingsClicked(it)
|
onRoomSettingsClicked(contextMenu.roomId)
|
||||||
},
|
},
|
||||||
onLeaveRoomClicked = {
|
onLeaveRoomClicked = {
|
||||||
eventSink(RoomListEvents.HideContextMenu)
|
eventSink(RoomListEvents.HideContextMenu)
|
||||||
|
|
@ -64,8 +72,10 @@ fun RoomListContextMenu(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomListModalBottomSheetContent(
|
private fun RoomListModalBottomSheetContent(
|
||||||
contextMenu: RoomListState.ContextMenu.Shown,
|
contextMenu: RoomListState.ContextMenu.Shown,
|
||||||
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
onRoomMarkReadClicked: () -> Unit,
|
||||||
onLeaveRoomClicked: (roomId: RoomId) -> Unit,
|
onRoomMarkUnreadClicked: () -> Unit,
|
||||||
|
onRoomSettingsClicked: () -> Unit,
|
||||||
|
onLeaveRoomClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
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(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -85,7 +127,7 @@ private fun RoomListModalBottomSheetContent(
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.clickable { onRoomSettingsClicked(contextMenu.roomId) },
|
modifier = Modifier.clickable { onRoomSettingsClicked() },
|
||||||
leadingContent = ListItemContent.Icon(
|
leadingContent = ListItemContent.Icon(
|
||||||
iconSource = IconSource.Vector(
|
iconSource = IconSource.Vector(
|
||||||
CompoundIcons.Settings,
|
CompoundIcons.Settings,
|
||||||
|
|
@ -96,14 +138,16 @@ private fun RoomListModalBottomSheetContent(
|
||||||
)
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
val leaveText = stringResource(id = if (contextMenu.isDm) {
|
val leaveText = stringResource(
|
||||||
CommonStrings.action_leave_conversation
|
id = if (contextMenu.isDm) {
|
||||||
} else {
|
CommonStrings.action_leave_conversation
|
||||||
CommonStrings.action_leave_room
|
} else {
|
||||||
})
|
CommonStrings.action_leave_room
|
||||||
|
}
|
||||||
|
)
|
||||||
Text(text = leaveText)
|
Text(text = leaveText)
|
||||||
},
|
},
|
||||||
modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) },
|
modifier = Modifier.clickable { onLeaveRoomClicked() },
|
||||||
leadingContent = ListItemContent.Icon(
|
leadingContent = ListItemContent.Icon(
|
||||||
iconSource = IconSource.Vector(
|
iconSource = IconSource.Vector(
|
||||||
CompoundIcons.Leave,
|
CompoundIcons.Leave,
|
||||||
|
|
@ -122,11 +166,9 @@ private fun RoomListModalBottomSheetContent(
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
||||||
RoomListModalBottomSheetContent(
|
RoomListModalBottomSheetContent(
|
||||||
contextMenu = RoomListState.ContextMenu.Shown(
|
contextMenu = aContextMenuShown(hasNewContent = true),
|
||||||
roomId = RoomId(value = "!aRoom:aDomain"),
|
onRoomMarkReadClicked = {},
|
||||||
roomName = "aRoom",
|
onRoomMarkUnreadClicked = {},
|
||||||
isDm = false,
|
|
||||||
),
|
|
||||||
onRoomSettingsClicked = {},
|
onRoomSettingsClicked = {},
|
||||||
onLeaveRoomClicked = {}
|
onLeaveRoomClicked = {}
|
||||||
)
|
)
|
||||||
|
|
@ -136,11 +178,9 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
|
internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
|
||||||
RoomListModalBottomSheetContent(
|
RoomListModalBottomSheetContent(
|
||||||
contextMenu = RoomListState.ContextMenu.Shown(
|
contextMenu = aContextMenuShown(isDm = true),
|
||||||
roomId = RoomId(value = "!aRoom:aDomain"),
|
onRoomMarkReadClicked = {},
|
||||||
roomName = "aRoom",
|
onRoomMarkUnreadClicked = {},
|
||||||
isDm = true,
|
|
||||||
),
|
|
||||||
onRoomSettingsClicked = {},
|
onRoomSettingsClicked = {},
|
||||||
onLeaveRoomClicked = {}
|
onLeaveRoomClicked = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ sealed interface RoomListEvents {
|
||||||
data object DismissRecoveryKeyPrompt : RoomListEvents
|
data object DismissRecoveryKeyPrompt : RoomListEvents
|
||||||
data object ToggleSearchResults : RoomListEvents
|
data object ToggleSearchResults : RoomListEvents
|
||||||
data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : 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.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
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.InviteStateDataSource
|
||||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
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.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
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.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.MatrixUser
|
||||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -65,9 +69,11 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val indicatorService: IndicatorService,
|
private val indicatorService: IndicatorService,
|
||||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||||
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListState {
|
override fun present(): RoomListState {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val leaveRoomState = leaveRoomPresenter.present()
|
val leaveRoomState = leaveRoomPresenter.present()
|
||||||
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
|
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
|
||||||
mutableStateOf(null)
|
mutableStateOf(null)
|
||||||
|
|
@ -105,6 +111,9 @@ class RoomListPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val markAsUnreadFeatureFlagEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MarkAsUnread)
|
||||||
|
.collectAsState(initial = null)
|
||||||
|
|
||||||
// Avatar indicator
|
// Avatar indicator
|
||||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||||
|
|
||||||
|
|
@ -129,10 +138,23 @@ class RoomListPresenter @Inject constructor(
|
||||||
roomId = event.roomListRoomSummary.roomId,
|
roomId = event.roomListRoomSummary.roomId,
|
||||||
roomName = event.roomListRoomSummary.name,
|
roomName = event.roomListRoomSummary.name,
|
||||||
isDm = event.roomListRoomSummary.isDm,
|
isDm = event.roomListRoomSummary.isDm,
|
||||||
|
markAsUnreadFeatureFlagEnabled = markAsUnreadFeatureFlagEnabled == true,
|
||||||
|
hasNewContent = event.roomListRoomSummary.hasNewContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
|
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
|
||||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
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 roomId: RoomId,
|
||||||
val roomName: String,
|
val roomName: String,
|
||||||
val isDm: Boolean,
|
val isDm: Boolean,
|
||||||
|
val markAsUnreadFeatureFlagEnabled: Boolean,
|
||||||
|
val hasNewContent: Boolean,
|
||||||
) : ContextMenu
|
) : ContextMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||||
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
||||||
aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()),
|
aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()),
|
||||||
aRoomListState().copy(displaySearchResults = true),
|
aRoomListState().copy(displaySearchResults = true),
|
||||||
aRoomListState().copy(
|
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||||
contextMenu = RoomListState.ContextMenu.Shown(
|
|
||||||
roomId = RoomId("!aRoom:aDomain"),
|
|
||||||
roomName = "A nice room name",
|
|
||||||
isDm = false,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
||||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
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,
|
numberOfUnreadMessages = 0,
|
||||||
numberOfUnreadMentions = 0,
|
numberOfUnreadMentions = 0,
|
||||||
numberOfUnreadNotifications = 0,
|
numberOfUnreadNotifications = 0,
|
||||||
|
isMarkedUnread = false,
|
||||||
userDefinedNotificationMode = null,
|
userDefinedNotificationMode = null,
|
||||||
hasRoomCall = false,
|
hasRoomCall = false,
|
||||||
isDm = false,
|
isDm = false,
|
||||||
|
|
@ -73,6 +74,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
||||||
numberOfUnreadMessages = roomSummary.details.numUnreadMessages,
|
numberOfUnreadMessages = roomSummary.details.numUnreadMessages,
|
||||||
numberOfUnreadMentions = roomSummary.details.numUnreadMentions,
|
numberOfUnreadMentions = roomSummary.details.numUnreadMentions,
|
||||||
numberOfUnreadNotifications = roomSummary.details.numUnreadNotifications,
|
numberOfUnreadNotifications = roomSummary.details.numUnreadNotifications,
|
||||||
|
isMarkedUnread = roomSummary.details.isMarkedUnread,
|
||||||
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
|
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
|
||||||
lastMessage = roomSummary.details.lastMessage?.let { message ->
|
lastMessage = roomSummary.details.lastMessage?.let { message ->
|
||||||
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ data class RoomListRoomSummary(
|
||||||
val numberOfUnreadMessages: Int,
|
val numberOfUnreadMessages: Int,
|
||||||
val numberOfUnreadMentions: Int,
|
val numberOfUnreadMentions: Int,
|
||||||
val numberOfUnreadNotifications: Int,
|
val numberOfUnreadNotifications: Int,
|
||||||
|
val isMarkedUnread: Boolean,
|
||||||
val timestamp: String?,
|
val timestamp: String?,
|
||||||
val lastMessage: CharSequence?,
|
val lastMessage: CharSequence?,
|
||||||
val avatarData: AvatarData,
|
val avatarData: AvatarData,
|
||||||
|
|
@ -38,9 +39,11 @@ data class RoomListRoomSummary(
|
||||||
val isDm: Boolean,
|
val isDm: Boolean,
|
||||||
) {
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0)
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
|
isMarkedUnread
|
||||||
|
|
||||||
val hasNewContent = numberOfUnreadMessages > 0 ||
|
val hasNewContent = numberOfUnreadMessages > 0 ||
|
||||||
numberOfUnreadMentions > 0 ||
|
numberOfUnreadMentions > 0 ||
|
||||||
numberOfUnreadNotifications > 0
|
numberOfUnreadNotifications > 0 ||
|
||||||
|
isMarkedUnread
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ internal fun aRoomListRoomSummary(
|
||||||
numberOfUnreadMessages: Int = 0,
|
numberOfUnreadMessages: Int = 0,
|
||||||
numberOfUnreadMentions: Int = 0,
|
numberOfUnreadMentions: Int = 0,
|
||||||
numberOfUnreadNotifications: Int = 0,
|
numberOfUnreadNotifications: Int = 0,
|
||||||
|
isMarkedUnread: Boolean = false,
|
||||||
lastMessage: String? = "Last message",
|
lastMessage: String? = "Last message",
|
||||||
timestamp: String? = lastMessage?.let { "88:88" },
|
timestamp: String? = lastMessage?.let { "88:88" },
|
||||||
isPlaceholder: Boolean = false,
|
isPlaceholder: Boolean = false,
|
||||||
|
|
@ -103,6 +104,7 @@ internal fun aRoomListRoomSummary(
|
||||||
numberOfUnreadMessages = numberOfUnreadMessages,
|
numberOfUnreadMessages = numberOfUnreadMessages,
|
||||||
numberOfUnreadMentions = numberOfUnreadMentions,
|
numberOfUnreadMentions = numberOfUnreadMentions,
|
||||||
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
numberOfUnreadNotifications = numberOfUnreadNotifications,
|
||||||
|
isMarkedUnread = isMarkedUnread,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
lastMessage = lastMessage,
|
lastMessage = lastMessage,
|
||||||
avatarData = avatarData,
|
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.leaveroom.fake.FakeLeaveRoomPresenter
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
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.FakeInviteDataSource
|
||||||
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
|
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.RoomListDataSource
|
||||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
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.InMemoryMigrationScreenStore
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
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.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.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.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
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.indicator.impl.DefaultIndicatorService
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
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.BackupState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
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.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
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.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
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.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
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.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.room.aRoomSummaryFilled
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
|
|
@ -175,12 +178,23 @@ class RoomListPresenterTests {
|
||||||
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
val initialItems = initialState.roomList.dataOrNull().orEmpty()
|
||||||
assertThat(initialItems.size).isEqualTo(16)
|
assertThat(initialItems.size).isEqualTo(16)
|
||||||
assertThat(initialItems.all { it.isPlaceholder }).isTrue()
|
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 withRoomState = consumeItemsUntilPredicate { state -> state.roomList.dataOrNull()?.size == 1 }.last()
|
||||||
val withRoomStateItems = withRoomState.roomList.dataOrNull().orEmpty()
|
val withRoomStateItems = withRoomState.roomList.dataOrNull().orEmpty()
|
||||||
assertThat(withRoomStateItems.size).isEqualTo(1)
|
assertThat(withRoomStateItems.size).isEqualTo(1)
|
||||||
assertThat(withRoomStateItems.first())
|
assertThat(withRoomStateItems.first()).isEqualTo(
|
||||||
.isEqualTo(aRoomListRoomSummary)
|
createRoomListRoomSummary(
|
||||||
|
numberOfUnreadMentions = 1,
|
||||||
|
numberOfUnreadMessages = 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +210,14 @@ class RoomListPresenterTests {
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
roomListService.postAllRooms(listOf(aRoomSummaryFilled()))
|
roomListService.postAllRooms(
|
||||||
|
listOf(
|
||||||
|
aRoomSummaryFilled(
|
||||||
|
numUnreadMentions = 1,
|
||||||
|
numUnreadMessages = 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
skipItems(3)
|
skipItems(3)
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
// Test filtering with result
|
// Test filtering with result
|
||||||
|
|
@ -207,8 +228,12 @@ class RoomListPresenterTests {
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
||||||
assertThat(withFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3))
|
assertThat(withFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3))
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
assertThat(withFilteredRoomState.filteredRoomList.size).isEqualTo(1)
|
||||||
assertThat(withFilteredRoomState.filteredRoomList.first())
|
assertThat(withFilteredRoomState.filteredRoomList.first()).isEqualTo(
|
||||||
.isEqualTo(aRoomListRoomSummary)
|
createRoomListRoomSummary(
|
||||||
|
numberOfUnreadMentions = 1,
|
||||||
|
numberOfUnreadMessages = 2,
|
||||||
|
)
|
||||||
|
)
|
||||||
// Test filtering without result
|
// Test filtering without result
|
||||||
withFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada"))
|
withFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada"))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
@ -322,12 +347,19 @@ class RoomListPresenterTests {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val summary = aRoomListRoomSummary
|
val summary = createRoomListRoomSummary()
|
||||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||||
|
|
||||||
val shownState = awaitItem()
|
val shownState = awaitItem()
|
||||||
assertThat(shownState.contextMenu)
|
assertThat(shownState.contextMenu).isEqualTo(
|
||||||
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
|
RoomListState.ContextMenu.Shown(
|
||||||
|
roomId = summary.roomId,
|
||||||
|
roomName = summary.name,
|
||||||
|
isDm = false,
|
||||||
|
markAsUnreadFeatureFlagEnabled = true,
|
||||||
|
hasNewContent = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -342,12 +374,19 @@ class RoomListPresenterTests {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val summary = aRoomListRoomSummary
|
val summary = createRoomListRoomSummary()
|
||||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||||
|
|
||||||
val shownState = awaitItem()
|
val shownState = awaitItem()
|
||||||
assertThat(shownState.contextMenu)
|
assertThat(shownState.contextMenu).isEqualTo(
|
||||||
.isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false))
|
RoomListState.ContextMenu.Shown(
|
||||||
|
roomId = summary.roomId,
|
||||||
|
roomName = summary.name,
|
||||||
|
isDm = false,
|
||||||
|
markAsUnreadFeatureFlagEnabled = true,
|
||||||
|
hasNewContent = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
shownState.eventSink(RoomListEvents.HideContextMenu)
|
shownState.eventSink(RoomListEvents.HideContextMenu)
|
||||||
|
|
||||||
val hiddenState = awaitItem()
|
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(
|
private fun TestScope.createRoomListPresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||||
|
|
@ -442,6 +516,7 @@ class RoomListPresenterTests {
|
||||||
},
|
},
|
||||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||||
|
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
|
|
@ -472,23 +547,6 @@ class RoomListPresenterTests {
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||||
),
|
),
|
||||||
migrationScreenPresenter = migrationScreenPresenter,
|
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
|
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
|
||||||
|
|
||||||
|
const val A_FORMATTED_DATE = "formatted_date"
|
||||||
|
|
||||||
class FakeLastMessageTimestampFormatter : LastMessageTimestampFormatter {
|
class FakeLastMessageTimestampFormatter : LastMessageTimestampFormatter {
|
||||||
private var format = ""
|
private var format = ""
|
||||||
fun givenFormat(format: String) {
|
fun givenFormat(format: String) {
|
||||||
|
|
|
||||||
|
|
@ -75,4 +75,11 @@ enum class FeatureFlags(
|
||||||
defaultValue = true,
|
defaultValue = true,
|
||||||
isFinished = false,
|
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.PinUnlock -> true
|
||||||
FeatureFlags.Mentions -> true
|
FeatureFlags.Mentions -> true
|
||||||
FeatureFlags.SecureStorage -> true
|
FeatureFlags.SecureStorage -> true
|
||||||
|
FeatureFlags.MarkAsUnread -> false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
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.poll.PollKind
|
||||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
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.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.MatrixWidgetDriver
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -150,6 +151,17 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit>
|
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.
|
* Share a location message in the room.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ data class RoomSummaryDetails(
|
||||||
val numUnreadMessages: Int,
|
val numUnreadMessages: Int,
|
||||||
val numUnreadMentions: Int,
|
val numUnreadMentions: Int,
|
||||||
val numUnreadNotifications: Int,
|
val numUnreadNotifications: Int,
|
||||||
|
val isMarkedUnread: Boolean,
|
||||||
val inviter: RoomMember?,
|
val inviter: RoomMember?,
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||||
val hasRoomCall: Boolean,
|
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.location.AssetType
|
||||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
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.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.MatrixWidgetDriver
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||||
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
|
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.location.toInner
|
||||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
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.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.util.mxCallbackFlow
|
||||||
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
||||||
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
|
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(
|
override suspend fun sendLocation(
|
||||||
body: String,
|
body: String,
|
||||||
geoUri: String,
|
geoUri: String,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
||||||
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
|
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
|
||||||
numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
|
numUnreadMessages = roomInfo.numUnreadMessages.toInt(),
|
||||||
numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
|
numUnreadNotifications = roomInfo.numUnreadNotifications.toInt(),
|
||||||
|
isMarkedUnread = roomInfo.isMarkedUnread,
|
||||||
lastMessage = latestRoomMessage,
|
lastMessage = latestRoomMessage,
|
||||||
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
|
inviter = roomInfo.inviter?.let(RoomMemberMapper::map),
|
||||||
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
|
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.StateEventType
|
||||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
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.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.timeline.item.event.EventTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||||
|
|
@ -374,6 +375,20 @@ class FakeMatrixRoom(
|
||||||
return reportContentResult
|
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(
|
override suspend fun sendLocation(
|
||||||
body: String,
|
body: String,
|
||||||
geoUri: String,
|
geoUri: String,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ fun aRoomSummaryFilled(
|
||||||
isDirect: Boolean = false,
|
isDirect: Boolean = false,
|
||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
lastMessage: RoomMessage? = aRoomMessage(),
|
lastMessage: RoomMessage? = aRoomMessage(),
|
||||||
numUnreadMentions: Int = 1,
|
numUnreadMentions: Int = 0,
|
||||||
numUnreadMessages: Int = 2,
|
numUnreadMessages: Int = 0,
|
||||||
notificationMode: RoomNotificationMode? = null,
|
notificationMode: RoomNotificationMode? = null,
|
||||||
) = RoomSummary.Filled(
|
) = RoomSummary.Filled(
|
||||||
aRoomSummaryDetails(
|
aRoomSummaryDetails(
|
||||||
|
|
@ -62,6 +62,7 @@ fun aRoomSummaryDetails(
|
||||||
numUnreadMentions: Int = 0,
|
numUnreadMentions: Int = 0,
|
||||||
numUnreadMessages: Int = 0,
|
numUnreadMessages: Int = 0,
|
||||||
numUnreadNotifications: Int = 0,
|
numUnreadNotifications: Int = 0,
|
||||||
|
isMarkedUnread: Boolean = false,
|
||||||
notificationMode: RoomNotificationMode? = null,
|
notificationMode: RoomNotificationMode? = null,
|
||||||
inviter: RoomMember? = null,
|
inviter: RoomMember? = null,
|
||||||
canonicalAlias: String? = null,
|
canonicalAlias: String? = null,
|
||||||
|
|
@ -76,6 +77,7 @@ fun aRoomSummaryDetails(
|
||||||
numUnreadMentions = numUnreadMentions,
|
numUnreadMentions = numUnreadMentions,
|
||||||
numUnreadMessages = numUnreadMessages,
|
numUnreadMessages = numUnreadMessages,
|
||||||
numUnreadNotifications = numUnreadNotifications,
|
numUnreadNotifications = numUnreadNotifications,
|
||||||
|
isMarkedUnread = isMarkedUnread,
|
||||||
userDefinedNotificationMode = notificationMode,
|
userDefinedNotificationMode = notificationMode,
|
||||||
inviter = inviter,
|
inviter = inviter,
|
||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ fun aRoomSummaryDetails(
|
||||||
numUnreadMentions: Int = 0,
|
numUnreadMentions: Int = 0,
|
||||||
numUnreadMessages: Int = 0,
|
numUnreadMessages: Int = 0,
|
||||||
numUnreadNotifications: Int = 0,
|
numUnreadNotifications: Int = 0,
|
||||||
|
isMarkedUnread: Boolean = false,
|
||||||
) = RoomSummaryDetails(
|
) = RoomSummaryDetails(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
name = name,
|
name = name,
|
||||||
|
|
@ -130,4 +131,5 @@ fun aRoomSummaryDetails(
|
||||||
numUnreadMentions = numUnreadMentions,
|
numUnreadMentions = numUnreadMentions,
|
||||||
numUnreadMessages = numUnreadMessages,
|
numUnreadMessages = numUnreadMessages,
|
||||||
numUnreadNotifications = numUnreadNotifications,
|
numUnreadNotifications = numUnreadNotifications,
|
||||||
|
isMarkedUnread = isMarkedUnread,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ dependencies {
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.dateformatter.impl)
|
implementation(projects.libraries.dateformatter.impl)
|
||||||
implementation(projects.libraries.eventformatter.impl)
|
implementation(projects.libraries.eventformatter.impl)
|
||||||
|
implementation(projects.libraries.preferences.impl)
|
||||||
implementation(projects.libraries.indicator.impl)
|
implementation(projects.libraries.indicator.impl)
|
||||||
implementation(projects.features.invitelist.impl)
|
implementation(projects.features.invitelist.impl)
|
||||||
implementation(projects.features.roomlist.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.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
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 io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
@ -108,7 +109,12 @@ class RoomListScreen(
|
||||||
migrationScreenPresenter = MigrationScreenPresenter(
|
migrationScreenPresenter = MigrationScreenPresenter(
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
migrationScreenStore = SharedPrefsMigrationScreenStore(context.getSharedPreferences("migration", Context.MODE_PRIVATE))
|
||||||
)
|
),
|
||||||
|
sessionPreferencesStore = DefaultSessionPreferencesStore(
|
||||||
|
context = context,
|
||||||
|
sessionId = matrixClient.sessionId,
|
||||||
|
sessionCoroutineScope = Singleton.appScope
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1bd5f590fd30facfb007050c26aa567d568ab0d797a27986b6f88a23af76f9dc
|
oid sha256:547fe148039b4f0c4e84897af1b950d372ddb6aa5d0d79b53ca05fbc7fbdc11f
|
||||||
size 14167
|
size 17388
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e609d908d159cffdc2a17ac8944413dbb52e5cd1ebdd2bd30057dc9865749bd6
|
oid sha256:2c603ba7cecfbb554b2c1c64bfba76f5c97d1c58c3adbbe69fb944592abca54a
|
||||||
size 13369
|
size 16287
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5c7e78d4f8fd595922f4bdce5263bf9c5cf7094df5edb9a0e9d35fe270b21d23
|
oid sha256:b142adcfc92ed9ddca5c8ec788c610b11296c128a577e5766ef1b10aa6a216cb
|
||||||
size 12541
|
size 15550
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:715154a104ef257a1865be35775cc7223e7adf3494863e0d24f1f1a8c7129920
|
oid sha256:f898bbf9dd2cc11c594261f08682d8cebaefe1b9d8e526793c6f23a51d0f696b
|
||||||
size 11888
|
size 14560
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue