Browse Source

Merge pull request #2389 from element-hq/renovate/org.matrix.rustcomponents-sdk-android-0.x

* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.1

* read : use the new apis

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: ganfra <francoisg@matrix.org>
Co-authored-by: Jorge Martín <jorgem@element.io>
pull/2394/head
Jorge Martin Espinosa 7 months ago committed by GitHub
parent
commit
aaa9b72308
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
  2. 12
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt
  3. 4
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
  4. 72
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
  5. 9
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  6. 8
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  7. 2
      gradle/libs.versions.toml
  8. 12
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
  9. 12
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
  10. 11
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

4
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

@ -159,10 +159,10 @@ class MessagesPresenter @AssistedInject constructor( @@ -159,10 +159,10 @@ class MessagesPresenter @AssistedInject constructor(
}
LaunchedEffect(Unit) {
// Mark the room as read on entering but don't send read receipts
// Remove the unread flag on entering but don't send read receipts
// as those will be handled by the timeline.
withContext(dispatchers.io) {
room.markAsRead(null)
room.setUnreadFlag(isUnread = false)
}
}

12
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt

@ -22,7 +22,6 @@ import androidx.compose.runtime.MutableState @@ -22,7 +22,6 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -90,7 +89,6 @@ class TimelinePresenter @AssistedInject constructor( @@ -90,7 +89,6 @@ class TimelinePresenter @AssistedInject constructor(
mutableStateOf(null)
}
val lastReadReceiptIndex = rememberSaveable { mutableIntStateOf(Int.MAX_VALUE) }
val lastReadReceiptId = rememberSaveable { mutableStateOf<EventId?>(null) }
val timelineItems by timelineItemsFactory.collectItemsAsState()
@ -128,7 +126,6 @@ class TimelinePresenter @AssistedInject constructor( @@ -128,7 +126,6 @@ class TimelinePresenter @AssistedInject constructor(
appScope.sendReadReceiptIfNeeded(
firstVisibleIndex = event.firstIndex,
timelineItems = timelineItems,
lastReadReceiptIndex = lastReadReceiptIndex,
lastReadReceiptId = lastReadReceiptId,
readReceiptType = if (isSendPublicReadReceiptsEnabled) ReceiptType.READ else ReceiptType.READ_PRIVATE,
)
@ -228,18 +225,21 @@ class TimelinePresenter @AssistedInject constructor( @@ -228,18 +225,21 @@ class TimelinePresenter @AssistedInject constructor(
private fun CoroutineScope.sendReadReceiptIfNeeded(
firstVisibleIndex: Int,
timelineItems: ImmutableList<TimelineItem>,
lastReadReceiptIndex: MutableState<Int>,
lastReadReceiptId: MutableState<EventId?>,
readReceiptType: ReceiptType,
) = launch(dispatchers.computation) {
// If we are at the bottom of timeline, we mark the room as read.
if (firstVisibleIndex == 0) {
room.markAsRead(receiptType = readReceiptType)
} else {
// Get last valid EventId seen by the user, as the first index might refer to a Virtual item
val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems)
if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex.value && eventId != lastReadReceiptId.value) {
lastReadReceiptIndex.value = firstVisibleIndex
if (eventId != null && eventId != lastReadReceiptId.value) {
lastReadReceiptId.value = eventId
timeline.sendReadReceipt(eventId = eventId, receiptType = readReceiptType)
}
}
}
private fun getLastEventIdBeforeOrAt(index: Int, items: ImmutableList<TimelineItem>): EventId? {
for (i in index until items.count()) {

4
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

@ -134,7 +134,7 @@ class MessagesPresenterTest { @@ -134,7 +134,7 @@ class MessagesPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - check that the room is marked as read`() = runTest {
fun `present - check that the room's unread flag is removed`() = runTest {
val room = FakeMatrixRoom()
assertThat(room.markAsReadCalls).isEmpty()
val presenter = createMessagesPresenter(matrixRoom = room)
@ -142,7 +142,7 @@ class MessagesPresenterTest { @@ -142,7 +142,7 @@ class MessagesPresenterTest {
presenter.present()
}.test {
runCurrent()
assertThat(room.markAsReadCalls).isEqualTo(listOf(null))
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false))
cancelAndIgnoreRemainingEvents()
}
}

72
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt

@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSende @@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSende
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
@ -60,8 +61,11 @@ import io.element.android.tests.testutils.awaitWithLatch @@ -60,8 +61,11 @@ import io.element.android.tests.testutils.awaitWithLatch
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@ -69,6 +73,7 @@ import java.util.Date @@ -69,6 +73,7 @@ import java.util.Date
import kotlin.time.Duration.Companion.seconds
private const val FAKE_UNIQUE_ID = "FAKE_UNIQUE_ID"
private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
class TimelinePresenterTest {
@get:Rule
@ -125,13 +130,47 @@ class TimelinePresenterTest { @@ -125,13 +130,47 @@ class TimelinePresenterTest {
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - on scroll finished send read receipt if an event is before the index`() = runTest {
fun `present - on scroll finished mark a room as read if the first visible index is 0`() = runTest(StandardTestDispatcher()) {
val timeline = FakeMatrixTimeline(
initialTimelineItems = listOf(
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
)
)
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
val room = FakeMatrixRoom(matrixTimeline = timeline)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
sessionPreferencesStore = sessionPreferencesStore,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(timeline.sentReadReceipts).isEmpty()
val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
runCurrent()
assertThat(room.markAsReadCalls).isNotEmpty()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - on scroll finished send read receipt if an event is before the index`() = runTest {
val timeline = FakeMatrixTimeline(
initialTimelineItems = listOf(
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
MatrixTimelineItem.Event(
uniqueId = FAKE_UNIQUE_ID_2,
event = anEventTimelineItem(
eventId = AN_EVENT_ID_2,
content = aMessageContent("Test message")
)
)
)
)
val presenter = createTimelinePresenter(timeline)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -140,7 +179,7 @@ class TimelinePresenterTest { @@ -140,7 +179,7 @@ class TimelinePresenterTest {
val initialState = awaitFirstItem()
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
}
assertThat(timeline.sentReadReceipts).isNotEmpty()
assertThat(timeline.sentReadReceipts.first().second).isEqualTo(ReceiptType.READ)
@ -149,10 +188,17 @@ class TimelinePresenterTest { @@ -149,10 +188,17 @@ class TimelinePresenterTest {
}
@Test
fun `present - on scroll finished send a private read receipt if an event is before the index and public read receipts are disabled`() = runTest {
fun `present - on scroll finished send a private read receipt if an event is at an index other than 0 and public read receipts are disabled`() = runTest {
val timeline = FakeMatrixTimeline(
initialTimelineItems = listOf(
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
MatrixTimelineItem.Event(
uniqueId = FAKE_UNIQUE_ID_2,
event = anEventTimelineItem(
eventId = AN_EVENT_ID_2,
content = aMessageContent("Test message")
)
)
)
)
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
@ -168,6 +214,7 @@ class TimelinePresenterTest { @@ -168,6 +214,7 @@ class TimelinePresenterTest {
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
}
assertThat(timeline.sentReadReceipts).isNotEmpty()
assertThat(timeline.sentReadReceipts.first().second).isEqualTo(ReceiptType.READ_PRIVATE)
@ -176,10 +223,17 @@ class TimelinePresenterTest { @@ -176,10 +223,17 @@ class TimelinePresenterTest {
}
@Test
fun `present - on scroll finished will not send read receipt if no event is before the index`() = runTest {
fun `present - on scroll finished will not send read receipt the first visible event is the same as before`() = runTest {
val timeline = FakeMatrixTimeline(
initialTimelineItems = listOf(
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem())
MatrixTimelineItem.Event(FAKE_UNIQUE_ID, anEventTimelineItem()),
MatrixTimelineItem.Event(
uniqueId = FAKE_UNIQUE_ID_2,
event = anEventTimelineItem(
eventId = AN_EVENT_ID_2,
content = aMessageContent("Test message")
)
)
)
)
val presenter = createTimelinePresenter(timeline)
@ -191,8 +245,9 @@ class TimelinePresenterTest { @@ -191,8 +245,9 @@ class TimelinePresenterTest {
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
}
assertThat(timeline.sentReadReceipts).isEmpty()
assertThat(timeline.sentReadReceipts).hasSize(1)
cancelAndIgnoreRemainingEvents()
}
}
@ -201,6 +256,7 @@ class TimelinePresenterTest { @@ -201,6 +256,7 @@ class TimelinePresenterTest {
fun `present - on scroll finished will not send read receipt only virtual events exist before the index`() = runTest {
val timeline = FakeMatrixTimeline(
initialTimelineItems = listOf(
MatrixTimelineItem.Virtual(FAKE_UNIQUE_ID, VirtualTimelineItem.ReadMarker),
MatrixTimelineItem.Virtual(FAKE_UNIQUE_ID, VirtualTimelineItem.ReadMarker)
)
)
@ -212,7 +268,7 @@ class TimelinePresenterTest { @@ -212,7 +268,7 @@ class TimelinePresenterTest {
val initialState = awaitFirstItem()
awaitWithLatch { latch ->
timeline.sendReadReceiptLatch = latch
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
}
assertThat(timeline.sentReadReceipts).isEmpty()
cancelAndIgnoreRemainingEvents()

9
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

@ -145,15 +145,20 @@ class RoomListPresenter @Inject constructor( @@ -145,15 +145,20 @@ class RoomListPresenter @Inject constructor(
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
is RoomListEvents.MarkAsRead -> coroutineScope.launch {
client.getRoom(event.roomId)?.use { room ->
room.setUnreadFlag(isUnread = false)
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
}
client.getRoom(event.roomId)?.markAsRead(receiptType)
room.markAsRead(receiptType)
}
}
is RoomListEvents.MarkAsUnread -> coroutineScope.launch {
client.getRoom(event.roomId)?.markAsUnread()
client.getRoom(event.roomId)?.use { room ->
room.setUnreadFlag(isUnread = true)
}
}
}
}

8
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt

@ -487,18 +487,18 @@ class RoomListPresenterTests { @@ -487,18 +487,18 @@ class RoomListPresenterTests {
}.test {
val initialState = awaitItem()
assertThat(room.markAsReadCalls).isEmpty()
assertThat(room.markAsUnreadReadCallCount).isEqualTo(0)
assertThat(room.setUnreadFlagCalls).isEmpty()
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ))
assertThat(room.markAsUnreadReadCallCount).isEqualTo(0)
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false))
initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID))
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ))
assertThat(room.markAsUnreadReadCallCount).isEqualTo(1)
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true))
// 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)
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true, false))
cancelAndIgnoreRemainingEvents()
scope.cancel()
}

2
gradle/libs.versions.toml

@ -152,7 +152,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" @@ -152,7 +152,7 @@ jsoup = "org.jsoup:jsoup:1.17.2"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.2"
timber = "com.jakewharton.timber:timber:5.0.1"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.0"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.1"
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }

12
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt

@ -153,15 +153,17 @@ interface MatrixRoom : Closeable { @@ -153,15 +153,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.
* Mark the room as read by trying to attach an unthreaded read receipt to the latest room event.
* @param receiptType The type of receipt to send.
*/
suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit>
suspend fun markAsRead(receiptType: ReceiptType): Result<Unit>
/**
* Sets a flag on the room to indicate that the user has explicitly marked it as unread.
* Sets a flag on the room to indicate that the user has explicitly marked it as unread, or reverts the flag.
* @param isUnread true to mark the room as unread, false to remove the flag.
*
*/
suspend fun markAsUnread(): Result<Unit>
suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit>
/**
* Share a location message in the room.

12
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt

@ -442,19 +442,15 @@ class RustMatrixRoom( @@ -442,19 +442,15 @@ class RustMatrixRoom(
}
}
override suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit> = withContext(roomDispatcher) {
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> = withContext(roomDispatcher) {
runCatching {
if (receiptType != null) {
innerRoom.markAsReadAndSendReadReceipt(receiptType.toRustReceiptType())
} else {
innerRoom.markAsRead()
}
innerRoom.markAsRead(receiptType.toRustReceiptType())
}
}
override suspend fun markAsUnread(): Result<Unit> = withContext(roomDispatcher) {
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.markAsUnread()
innerRoom.setUnreadFlag(isUnread)
}
}

11
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

@ -378,17 +378,18 @@ class FakeMatrixRoom( @@ -378,17 +378,18 @@ class FakeMatrixRoom(
return reportContentResult
}
val markAsReadCalls = mutableListOf<ReceiptType?>()
override suspend fun markAsRead(receiptType: ReceiptType?): Result<Unit> {
val markAsReadCalls = mutableListOf<ReceiptType>()
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> {
markAsReadCalls.add(receiptType)
return Result.success(Unit)
}
var markAsUnreadReadCallCount = 0
var setUnreadFlagCalls = mutableListOf<Boolean>()
private set
override suspend fun markAsUnread(): Result<Unit> {
markAsUnreadReadCallCount++
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> {
setUnreadFlagCalls.add(isUnread)
return Result.success(Unit)
}

Loading…
Cancel
Save